Skip to content
Merged
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
3 changes: 2 additions & 1 deletion entangled/commands/brei.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import textwrap

from ..config import Config, read_config
from ..io import FileCache
from brei import resolve_tasks, Phony
from ..logging import logger
from .main import main
Expand All @@ -18,7 +19,7 @@ async def brei_main(target_strs: list[str], force_run: bool, throttle: int | Non
if not Path(".entangled").exists():
Path(".entangled").mkdir()

cfg = Config() | read_config()
cfg = Config() | read_config(FileCache())
db = await resolve_tasks(cfg.brei, Path(".entangled/brei_history"))
if throttle:
db.throttle = asyncio.Semaphore(throttle)
Expand Down
3 changes: 1 addition & 2 deletions entangled/commands/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,4 @@ def main(version: bool = False, debug: bool = False):
sys.exit(0)

configure(debug)
logger().debug(f"Welcome to Entangled v{__version__}!")

logger().info(f"Welcome to Entangled v{__version__}!")
17 changes: 6 additions & 11 deletions entangled/commands/reset.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,10 @@ def reset():
Resets the file database. This performs a tangle without actually
writing output to the files, but updating the database as if we were.
"""

try:
doc = Document()
mode = TransactionMode.RESETDB
doc = Document()
mode = TransactionMode.RESETDB

with transaction(mode) as t:
doc.load(t)
doc.tangle(t)
t.clear_orphans()

except UserError as e:
logging.error(str(e))
with transaction(mode) as t:
doc.load(t)
doc.tangle(t)
t.clear_orphans()
6 changes: 4 additions & 2 deletions entangled/commands/status.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from collections.abc import Iterable
from ..status import list_dependent_files
from ..config import Config, read_config, get_input_files
from ..io import FileCache
from pathlib import Path

from rich.console import Console, Group
Expand Down Expand Up @@ -30,7 +31,8 @@ def files_panel(file_list: Iterable[Path], title: str) -> Panel:


def rich_status():
cfg = Config() | read_config()
fs = FileCache()
cfg = Config() | read_config(fs)
config_table = Table()
config_table.add_column("name")
config_table.add_column("value")
Expand All @@ -47,7 +49,7 @@ def rich_status():
Panel(config_table, title="config", border_style="dark_cyan"),
Columns(
[
files_panel(get_input_files(cfg), "input files"),
files_panel(get_input_files(fs, cfg), "input files"),
files_panel(list_dependent_files(), "dependent files"),
]
),
Expand Down
14 changes: 5 additions & 9 deletions entangled/commands/stitch.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,9 @@ def stitch(*, force: bool = False, show: bool = False):
else:
mode = TransactionMode.FAIL

try:
doc = Document()
doc = Document()

with transaction(mode) as t:
doc.load(t)
doc.load_all_code(t)
doc.stitch(t)

except UserError as e:
e.handle()
with transaction(mode) as t:
doc.load(t)
doc.load_all_code(t)
doc.stitch(t)
29 changes: 12 additions & 17 deletions entangled/commands/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ class Action(Enum):


def sync_action(doc: Document) -> Action:
input_file_list = doc.input_files()
fs = FileCache()
input_file_list = doc.input_files(fs)

with filedb(readonly=True) as db:
changed = set(db.changed_files(fs))
Expand Down Expand Up @@ -59,29 +59,24 @@ def stitch(doc: Document):
doc.tangle(t)
for h in doc.context.all_hooks:
h.post_tangle(doc.reference_map)


def run_sync():
try:
doc = Document()
match sync_action(doc):
case Action.TANGLE:
logging.info("Tangling.")
tangle(doc)

case Action.STITCH:
logging.info("Stitching.")
stitch(doc)
def run_sync():
doc = Document()
match sync_action(doc):
case Action.TANGLE:
logging.info("Tangling.")
tangle(doc)

case Action.NOTHING:
pass
case Action.STITCH:
logging.info("Stitching.")
stitch(doc)

except UserError as e:
e.handle()
case Action.NOTHING:
pass


@main.command()
def sync():
"""Be smart wether to tangle or stich"""
run_sync()

18 changes: 7 additions & 11 deletions entangled/commands/tangle.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,12 @@ def tangle(*, annotate: AnnotationMethod | None = None, force: bool = False, sho
else:
mode = TransactionMode.FAIL

try:
doc = Document()
doc = Document()

with transaction(mode) as t:
doc.load(t)
doc.tangle(t, annotate)
t.clear_orphans()
with transaction(mode) as t:
doc.load(t)
doc.tangle(t, annotate)
t.clear_orphans()

for h in doc.context.all_hooks:
h.post_tangle(doc.reference_map)

except UserError as e:
e.handle()
for h in doc.context.all_hooks:
h.post_tangle(doc.reference_map)
8 changes: 5 additions & 3 deletions entangled/commands/watch.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,17 @@ def _watch(_stop_event: Event | None = None, _start_event: Event | None = None):
def stop() -> bool:
return _stop_event is not None and _stop_event.is_set()

log.debug("Running daemon")
run_sync()

if _start_event:
if _start_event is not None:
log.debug("Setting start event")
_start_event.set()

dirs = "." # find_watch_dirs()

for changes in watchfiles.watch(dirs, stop_event=_stop_event, watch_filter=watch_filter):
log.debug(changes)
log.debug(changes)
run_sync()


Expand Down
54 changes: 32 additions & 22 deletions entangled/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@
from ..logging import logger
from ..version import __version__
from ..errors.user import HelpfulUserError

from ..io import AbstractFileCache

log = logger()


def read_config_from_toml(
path: Path, section: str | None = None
fs: AbstractFileCache, path: Path, section: str | None = None
) -> ConfigUpdate | None:
"""Read a config from given `path` in given `section`. The path should refer to
a TOML file that should decode to a `Config` object. If `section` is given, only
Expand All @@ -39,17 +39,17 @@ def read_config_from_toml(
read_config_from_toml(Path("./pyproject.toml"), "tool.entangled")
```
"""
if not path.exists():
if path not in fs:
return None
try:
with open(path, "rb") as f:
json: Any = tomllib.load(f) # pyright: ignore[reportExplicitAny]
if section is not None:
for s in section.split("."):
json = json[s] # pyright: ignore[reportAny]
update = msgspec.convert(json, type=ConfigUpdate)
log.debug("Read config from `%s`", path)
return update
content = fs[path].content
json: Any = tomllib.loads(content)
if section is not None:
for s in section.split("."):
json = json[s] # pyright: ignore[reportAny]
update = msgspec.convert(json, type=ConfigUpdate)
log.debug("Read config from `%s`", path)
return update

except (msgspec.ValidationError, tomllib.TOMLDecodeError) as e:
raise HelpfulUserError(f"Could not read config: {e}")
Expand All @@ -59,23 +59,33 @@ def read_config_from_toml(
return None


def read_config() -> ConfigUpdate | None:
if Path("./entangled.toml").exists():
return read_config_from_toml(Path("./entangled.toml"))
if Path("./pyproject.toml").exists():
def read_config(fs: AbstractFileCache) -> ConfigUpdate | None:
"""
Read configuration from any of the possible hard-coded locations:

- `./entangled.toml`
- `./pyproject.toml` section `[tool.entangled]`.

Returns a `ConfigUpdate` or `None`. To get the full `Config` object,
run `Config() | read_config(fs)`.
"""
if Path("./entangled.toml") in fs:
return read_config_from_toml(fs, Path("./entangled.toml"))
if Path("./pyproject.toml") in fs:
return (
read_config_from_toml(Path("./pyproject.toml"), "tool.entangled")
read_config_from_toml(fs, Path("./pyproject.toml"), "tool.entangled")
)
return None


def get_input_files(cfg: Config) -> list[Path]:
def get_input_files(fs: AbstractFileCache, cfg: Config) -> list[Path]:
"""
Get a sorted list of all input files for this project.
"""
log.debug("watch list: %s; ignoring: %s", cfg.watch_list, cfg.ignore_list)
include_file_list = chain.from_iterable(map(Path(".").glob, cfg.watch_list))
input_file_list = [
path for path in include_file_list
if not any(path.match(pat) for pat in cfg.ignore_list) and path.is_file()
]
input_file_list = filter(
lambda p: not any(p.match(pat) for pat in cfg.ignore_list),
chain.from_iterable(map(fs.glob, cfg.watch_list)))
log.debug("input file list %s", input_file_list)
return sorted(input_file_list)

Expand Down
7 changes: 4 additions & 3 deletions entangled/hooks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,15 @@
} | external_hooks


def create_hook(cfg: Config, h: str) -> HookBase | None:
def create_hook(cfg: Config, h: str, state: HookBase.State) -> HookBase | None:
if h not in hooks:
logging.error("hook `%s` not found", h)
return None

try:
hook_cfg = msgspec.convert(cfg.hook.get(h, {}), type=hooks[h].Config)
hook_instance = hooks[h](hook_cfg)
hook_cls = hooks[h]
hook_cfg = msgspec.convert(cfg.hook.get(h, {}), type=hook_cls.Config)
hook_instance = hook_cls(hook_cfg, state)
hook_instance.check_prerequisites()
return hook_instance
except PrerequisitesFailed as e:
Expand Down
5 changes: 4 additions & 1 deletion entangled/hooks/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ class HookBase:
class Config(Struct):
pass

def __init__(self, config: Config):
class State:
pass

def __init__(self, config: Config, state: State):
pass

@staticmethod
Expand Down
18 changes: 11 additions & 7 deletions entangled/hooks/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@
"""

from __future__ import annotations
from dataclasses import dataclass
from msgspec import field
from dataclasses import dataclass, field
from pathlib import Path, PurePath
from subprocess import run, SubprocessError, DEVNULL
import logging
from typing import final, override

import msgspec

from ..config.language import Language
from ..io import Transaction
from ..model.properties import Property, get_attribute, get_attribute_string, get_classes
Expand Down Expand Up @@ -45,7 +46,7 @@
@final
class Hook(HookBase):
class Config(HookBase.Config):
runners: dict[str, str] = field(default_factory=dict)
runners: dict[str, str] = msgspec.field(default_factory=dict)

def __post_init__(self):
for k, v in EXEC_CMDS.items():
Expand All @@ -64,9 +65,13 @@ def to_makefile(self, config: Hook.Config):
exec_cmd = config.runners[self.language.name].format(script=self.scriptfile)
return f"{self.target}: {self.scriptfile} {dep_str}\n" + f"\t{exec_cmd}"

def __init__(self, config: Hook.Config):
super().__init__(config)
self.recipes: list[Hook.Recipe] = []
@dataclass
class State(HookBase.State):
recipes: list[Hook.Recipe] = field(default_factory=list)

def __init__(self, config: Hook.Config, state: Hook.State):
super().__init__(config, state)
self.recipes: list[Hook.Recipe] = state.recipes
self.config = config

@override
Expand Down Expand Up @@ -108,4 +113,3 @@ def on_tangle(self, t: Transaction, refs: ReferenceMap):
rules="\n\n".join(r.to_makefile(self.config) for r in self.recipes),
)
t.write(Path(".entangled/build/Makefile"), makefile, [])

4 changes: 2 additions & 2 deletions entangled/hooks/quarto_attributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ def amend_code_properties(code_block: CodeBlock):

@final
class Hook(HookBase):
def __init__(self, config: Hook.Config):
super().__init__(config)
def __init__(self, config: Hook.Config, state: Hook.State):
super().__init__(config, state)
self.config = config

@override
Expand Down
Loading
Loading