Skip to content

Commit

Permalink
feature(actions): Docker Build Should Ignore Extras.
Browse files Browse the repository at this point in the history
For instance, ``yaml``, the ``dev`` folder, etc.
  • Loading branch information
acederberg committed Feb 7, 2025
1 parent 967b6b2 commit 2b09f46
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 36 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,4 @@ blog/projects/blog/quartodoc
blog/dsa/leetcode/calendar_2/_oops_files/
.env.actions
blog/posts/keywords/*.svg
blog/build-prod
136 changes: 120 additions & 16 deletions acederbergio/api/quarto.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,11 @@
import asyncio
import os
import pathlib
import shutil
import subprocess
import time
from typing import (
Annotated,
Any,
AsyncGenerator,
ClassVar,
Iterable,
Iterator,
Optional,
)
from typing import (Annotated, Any, AsyncGenerator, ClassVar, Iterable,
Iterator, Optional)

import bson
import motor
Expand Down Expand Up @@ -146,6 +140,17 @@ def wrapper(v):
class ConfigFilter(pydantic.BaseModel):
"""Configuration settings for ``Filter``."""

suffixes_included: Annotated[
set[str] | None,
pydantic.Field(
None,
description="""
Include only certain extension types.
This is most useful durring builds.
""",
),
]

filters: Annotated[
set[pathlib.Path],
pydantic.Field(default_factory=set, validate_default=True),
Expand Down Expand Up @@ -358,6 +363,7 @@ def __init__(
assets: Iterable[pathlib.Path] | None = None,
static: Iterable[pathlib.Path] | None = None,
ignore: Iterable[pathlib.Path] | None = None,
# suffixes_included: Iterable[str] | None = None
) -> None:

config = context.config.filter
Expand All @@ -366,6 +372,7 @@ def __init__(
self.assets = self.__validate_trie(assets or set(), config.assets)
self.static = self.__validate_trie(static or set(), config.static)
self.ignore = self.__validate_trie(ignore or set(), config.ignore)
# self.suffixes_included = set(suffixes_included) if suffixes_included is not None else None

self.tt_tolerance = tt_tolerance
self.tt_last = dict()
Expand Down Expand Up @@ -405,7 +412,10 @@ def is_ignored(self, path: pathlib.Path) -> tuple[bool, str | None]:
return True, "explicit"
elif path.is_dir():
return True, "directory"
elif path.suffix not in self.suffixes:
elif path.suffix not in self.suffixes or (
self.config.suffixes_included is not None
and path.suffix not in self.config.suffixes_included
):
return True, f"suffix={path.suffix}"
elif self.is_event_from_conform(path):
return True, "too close"
Expand Down Expand Up @@ -767,7 +777,11 @@ def walk(
if os.path.isdir(path):
for item in os.listdir(path):
yield from self.walk(path / item, depth=depth, depth_max=depth_max + 1)
elif os.path.isfile(path) and not self.filter.is_ignored(path)[0]:

return

is_ignored, _ = self.filter.is_ignored(path)
if os.path.isfile(path) and not is_ignored:
yield path

return
Expand Down Expand Up @@ -796,11 +810,11 @@ async def render(
:param callback: Optional callback.
"""

def resolve(data: schemas.QuartoHandlerAny | None) -> schemas.QuartoHandlerAny:
def resolve(data: schemas.QuartoHandlerAny | None, item: schemas.QuartoRenderRequestItem) -> schemas.QuartoHandlerAny:
return data if data is not None else schemas.QuartoHandlerRequest(data=item)

def do_break(data: schemas.QuartoHandlerAny):
if data.kind == "request":
if data.kind == "request" or data.kind == "job":
return False

if data.data.status_code and render_data.exit_on_failure: # type: ignore
Expand All @@ -810,9 +824,10 @@ def do_break(data: schemas.QuartoHandlerAny):

# TODO: Could be dryer.
data: schemas.QuartoHandlerAny
item: schemas.QuartoRenderRequestItem
for item in render_data.items:
if item.kind == "file":
yield (data := resolve(await self(item.path)))
yield (data := resolve(await self(item.path), item))
# if data is None:
# yield
# ignored.append(item)
Expand All @@ -832,7 +847,7 @@ def do_break(data: schemas.QuartoHandlerAny):
depth_max=item.directory_depth_max,
)
async for data in iter_directory:
yield (data := resolve(data))
yield (data := resolve(data, item))

# if isinstance(data, schemas.QuartoRenderRequestItem):
# ignored.append(data)
Expand Down Expand Up @@ -1033,7 +1048,7 @@ def cmd_context_test(

def add_paths(paths: Iterable[pathlib.Path], depth=0, rows=0) -> int:
"""
Returns the numver of rows encountered so far so that the table
Returns the numer of rows encountered so far so that the table
is not of unreasonable size, e.g. listing something stupid.
"""

Expand Down Expand Up @@ -1072,6 +1087,95 @@ def add_paths(paths: Iterable[pathlib.Path], depth=0, rows=0) -> int:
rich.print(t)


@cli.command("build")
def cmd_build(
_context: typer.Context,
):
"""Specifically for docker builds.
Should:
1. Not require a mongodb connection,
2. Not handler configuration (see `acederbergio config`),
3. Not output unnecessary assets (e.g. `yaml`, `dev` folder), which are
useful in development mode.
4. Not generate documentation.
4. Just copy over the `javascript` folder.
"""

context = Context(
config=Config(
filter=ConfigFilter.model_validate(
dict(
suffixes_included={".qmd", ".svg", ".json"},
# NOTE: These will be ignored during `walk`.
ignore=[env.BLOG / "dev", env.BLOG / "js"],
)
),
handler=ConfigHandler.model_validate(
dict(
verbose=False,
render=True,
flags=["--output-dir", "build-prod"],
)
),
),
database=db.Config.model_validate(dict(include=False)),
)
util.print_yaml(context.dict())

watch = Watch(context, include_mongo=False)
data = schemas.QuartoRenderRequest(
exit_on_failure=False,
items=[
schemas.QuartoRenderRequestItem(
path="blog",
kind="directory",
directory_depth_max=10,
)
],
)
TT = schemas.QuartoRender
SS = schemas.QuartoRenderResponse[TT] # type: ignore

async def callback(item: schemas.QuartoHandlerResult):
if item.kind == "render":
print(f"Rendering {item.data.target}.")
elif item.kind == "request":
print(f"Ignored `{item.data.model_dump(mode='json')}`.")

return

async def doit():

# NOTE: Copy over JS.
js_raw = env.BLOG / "js"
js_build = (env.BUILD / "js").resolve(strict=False)
print(js_build)
if js_build.exists():
rich.print(
f"[yellow]Removing existing javascript from build at `{js_build}`."
)
shutil.rmtree(js_build)

rich.print(f"[green]Copying javascript at `{js_raw}` to `{js_build}`...")
shutil.copytree(js_raw, js_build)

# NOTE: Render all `qmd`, `json`, and `svg` content.
# TODO: Once the handler emits jobs, I would like the jobs to be
# computed first so that a progress bar can be added here.
handler = await watch.get_handler()
result = await SS.fromHandlerResults(handler.render(data), callback=callback)

with open(env.BUILD / "quarto_renders.yaml", "w") as file:
yaml.dump(result, file)

# util.print_yaml(result)
return result

asyncio.run(doit())


@cli.command("render")
def cmd_render(
_context: typer.Context,
Expand Down
22 changes: 13 additions & 9 deletions blog/dev/plan.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -181,15 +181,19 @@ asyncio.run(main())

::: { #tbl-todo-handler }

| Item | Completed | Description |
| -------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------- |
| `POST /render` | | should return scheduled jobs |
| `WEBSOCKET` | | Should have a single database listener so that clients don't have to ping. |
| `Handler.listen` | | Context manager to allow others to listen, e.g. the above. Should emit `HandlerResult`. |
| `Handler.schedule` | | Should be used by `POST /render` to schedule a job. |
| `Handler.eval` | | Evaluate a scheduled job, mark started job. |
| `Handler.idle` | | Tell the handler background task to idle or stop go. This means that it would be possible to pause the listener. |
| `QuartoRender*` time attrs | | Should contain scheduled time, evaluated time, and completed time. |
| Item | Completed | Description |
| --------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------- |
| `POST /render` | | should return scheduled jobs |
| `WEBSOCKET` | | Should have a single database listener so that clients don't have to ping. |
| `Handler.listen` | | Context manager to allow others to listen, e.g. the above. Should emit `HandlerResult`. |
| `Handler.schedule` | | Should be used by `POST /render` to schedule a job. |
| `Handler.eval` | | Evaluate a scheduled job, mark started job. |
| `Handler.idle` | | Tell the handler background task to idle or stop go. This means that it would be possible to pause the listener. |
| `QuartoRender*` time attrs | | Should contain scheduled time, evaluated time, and completed time. |
| `Ignored` handler data type | | Should say when handler data is ignored, and it should provide 'reason' of `is_ignored` output. |
| `Handler.walk` -> Jobs | | `Handler.walk` should create jobs (instead of paths) and the `Ignored` handler data type (instead of `None`) |
| Lazy full builds | | Use hashes of `depends_on` and input to determine if rerender is necessary. |
| Document metadata Stored | | Keep live metadata to determine what watch does for some files. |

: Objectives Handler { .todo-table }

Expand Down
4 changes: 0 additions & 4 deletions blog/posts/nvim-notes.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@
title: Neovim Queries
---

```default
delete this later, it is a tes.
```

## Highlighting Quarto Metadata

First and formost, it is much easier to write queries when using the `:Inspect`
Expand Down
8 changes: 1 addition & 7 deletions docker/dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -165,13 +165,7 @@ RUN \
&& mkdir --parent /quarto/app/config /root/config \
&& poetry run acederbergio config --for-real all \
&& poetry run acederbergio docs python \
&& poetry run acederbergio quarto render \
--directory blog \
--mongo-exclude \
--output /quarto/app/blog/build/quarto-renders.yaml \
--silent \
--on-failure-exit \
--successes-exclude"
&& poetry run acederbergio quarto build"

# end snippet builder

Expand Down
3 changes: 3 additions & 0 deletions tests/scripts/test_dev.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ def filter() -> quarto.Filter:
# NOTE: These have bad extensions.
(env.BLOG / "index.ts", (True, "suffix=.ts")),
(env.ROOT / "foo.go", (True, "suffix=.go")),
#
# NOTE: SVGS should not be ignored.
(env.BLOG / "icons/favicon.svg", (False, None)),
),
)
def test_context(filter: quarto.Filter, case: pathlib.Path, result: tuple[bool, str]):
Expand Down

0 comments on commit 2b09f46

Please sign in to comment.