entangled-cli 2.3.2__py3-none-any.whl → 2.4.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. entangled/commands/__init__.py +5 -11
  2. entangled/commands/brei.py +17 -10
  3. entangled/commands/main.py +23 -0
  4. entangled/commands/new.py +35 -41
  5. entangled/commands/reset.py +7 -49
  6. entangled/commands/status.py +13 -7
  7. entangled/commands/stitch.py +12 -49
  8. entangled/commands/sync.py +57 -27
  9. entangled/commands/tangle.py +30 -56
  10. entangled/commands/watch.py +20 -39
  11. entangled/config/__init__.py +48 -153
  12. entangled/config/annotation_method.py +17 -0
  13. entangled/config/config_data.py +105 -0
  14. entangled/config/config_update.py +60 -0
  15. entangled/config/document_style.py +18 -0
  16. entangled/config/language.py +2 -1
  17. entangled/config/markers.py +21 -0
  18. entangled/config/namespace_default.py +14 -0
  19. entangled/config/version.py +5 -6
  20. entangled/errors/user.py +20 -21
  21. entangled/hooks/__init__.py +20 -21
  22. entangled/hooks/base.py +9 -2
  23. entangled/hooks/build.py +22 -19
  24. entangled/hooks/quarto_attributes.py +25 -17
  25. entangled/hooks/repl.py +18 -8
  26. entangled/hooks/shebang.py +5 -4
  27. entangled/hooks/spdx_license.py +5 -4
  28. entangled/hooks/task.py +36 -45
  29. entangled/interface/__init__.py +4 -0
  30. entangled/interface/context.py +63 -0
  31. entangled/interface/document.py +106 -0
  32. entangled/io/__init__.py +2 -2
  33. entangled/io/filedb.py +38 -18
  34. entangled/io/stat.py +2 -3
  35. entangled/io/transaction.py +26 -20
  36. entangled/io/virtual.py +93 -6
  37. entangled/iterators/__init__.py +6 -0
  38. entangled/iterators/lines.py +28 -0
  39. entangled/iterators/peekable.py +43 -0
  40. entangled/iterators/run_generator.py +14 -0
  41. entangled/main.py +4 -27
  42. entangled/model/__init__.py +23 -0
  43. entangled/model/code_block.py +79 -0
  44. entangled/model/content.py +34 -0
  45. entangled/{properties.py → model/properties.py} +21 -1
  46. entangled/model/reference_id.py +31 -0
  47. entangled/model/reference_map.py +102 -0
  48. entangled/model/reference_name.py +32 -0
  49. entangled/model/tangle.py +158 -0
  50. entangled/readers/__init__.py +16 -0
  51. entangled/readers/code.py +107 -0
  52. entangled/readers/delimiters.py +91 -0
  53. entangled/readers/markdown.py +179 -0
  54. entangled/readers/types.py +14 -0
  55. entangled/readers/yaml_header.py +56 -0
  56. entangled/status.py +12 -17
  57. entangled/text_location.py +11 -2
  58. entangled/utility.py +0 -4
  59. {entangled_cli-2.3.2.dist-info → entangled_cli-2.4.2.dist-info}/METADATA +8 -8
  60. entangled_cli-2.4.2.dist-info/RECORD +71 -0
  61. {entangled_cli-2.3.2.dist-info → entangled_cli-2.4.2.dist-info}/WHEEL +1 -1
  62. entangled/code_reader.py +0 -81
  63. entangled/document.py +0 -162
  64. entangled/from_str.py +0 -27
  65. entangled/markdown_reader.py +0 -229
  66. entangled/tangle.py +0 -153
  67. entangled_cli-2.3.2.dist-info/RECORD +0 -49
  68. {entangled_cli-2.3.2.dist-info → entangled_cli-2.4.2.dist-info}/entry_points.txt +0 -0
  69. {entangled_cli-2.3.2.dist-info → entangled_cli-2.4.2.dist-info}/licenses/LICENSE +0 -0
@@ -1,19 +1,13 @@
1
- from .new import new
2
- from .status import status
1
+ from .main import main
3
2
  from .stitch import stitch
4
- from .sync import sync
5
3
  from .tangle import tangle
4
+ from .new import new
6
5
  from .reset import reset
6
+ from .sync import sync
7
7
  from .watch import watch
8
+ from .status import status
8
9
  from .brei import brei
9
10
 
10
11
  __all__ = [
11
- "new",
12
- "brei",
13
- "reset",
14
- "status",
15
- "stitch",
16
- "sync",
17
- "tangle",
18
- "watch",
12
+ "main", "stitch", "tangle", "new", "reset", "sync", "watch", "status", "brei"
19
13
  ]
@@ -1,22 +1,26 @@
1
1
  from pathlib import Path
2
2
  from collections.abc import Awaitable
3
3
  from typing import Any
4
- import argh # type: ignore
5
4
  import asyncio
6
5
  import textwrap
7
6
 
8
- from ..config import config
7
+ from ..config import Config, read_config
8
+ from ..io import FileCache
9
9
  from brei import resolve_tasks, Phony
10
10
  from ..logging import logger
11
+ from .main import main
12
+
13
+ import click
11
14
 
12
15
  log = logger()
13
16
 
14
17
 
15
- async def main(target_strs: list[str], force_run: bool, throttle: int | None):
18
+ async def brei_main(target_strs: list[str], force_run: bool, throttle: int | None):
16
19
  if not Path(".entangled").exists():
17
20
  Path(".entangled").mkdir()
18
21
 
19
- db = await resolve_tasks(config.get.brei, Path(".entangled/brei_history"))
22
+ cfg = Config() | read_config(FileCache())
23
+ db = await resolve_tasks(cfg.brei, Path(".entangled/brei_history"))
20
24
  if throttle:
21
25
  db.throttle = asyncio.Semaphore(throttle)
22
26
  db.force_run = force_run
@@ -33,10 +37,13 @@ async def main(target_strs: list[str], force_run: bool, throttle: int | None):
33
37
  log.error(msg)
34
38
 
35
39
 
36
- @argh.arg("targets", nargs="+", help="name of target to run")
37
- @argh.arg("-B", "--force-run", help="rebuild all dependencies")
38
- @argh.arg("-j", "--throttle", help="limit number of concurrent jobs")
40
+ @main.command()
41
+ @click.argument("targets", nargs=-1)
42
+ @click.option("-B", "--force-run", is_flag=True, help="rebuild all dependencies")
43
+ @click.option("-j", "--throttle", is_flag=True, help="limit number of concurrent jobs")
39
44
  def brei(targets: list[str], *, force_run: bool = False, throttle: int | None = None):
40
- """Build one of the configured targets."""
41
- config.read()
42
- asyncio.run(main(targets, force_run, throttle))
45
+ """Build one of the configured targets.
46
+
47
+ TARGETS Names of the targets to run.
48
+ """
49
+ asyncio.run(brei_main(targets, force_run, throttle))
@@ -0,0 +1,23 @@
1
+ from ..logging import configure, logger
2
+ from ..version import __version__
3
+
4
+ import sys
5
+ import rich_click as click
6
+
7
+
8
+ @click.group(
9
+ invoke_without_command=True,
10
+ epilog="See https://entangled.github.io/ for more help and tutorials."
11
+ )
12
+ @click.rich_config({"commands_before_options": True, "theme": "nord-modern"})
13
+ @click.option("-v", "--version", is_flag=True, help="Show version.")
14
+ @click.option("-d", "--debug", is_flag=True, help="Enable debugging.")
15
+ def main(version: bool = False, debug: bool = False):
16
+ """Your literate programming toolkit.
17
+ """
18
+ if version:
19
+ print(f"Entangled {__version__}")
20
+ sys.exit(0)
21
+
22
+ configure(debug)
23
+ logger().info(f"Welcome to Entangled v{__version__}!")
entangled/commands/new.py CHANGED
@@ -1,13 +1,11 @@
1
- import argh # type: ignore
2
- from argh.utils import get_subparsers
3
- import argparse
1
+ import click
4
2
 
5
3
  from pathlib import Path
6
- from rich_argparse import RichHelpFormatter
7
4
  from rich.console import Console
8
5
  from rich.table import Table
9
6
 
10
7
  from ..errors.user import HelpfulUserError
8
+ from .main import main
11
9
 
12
10
  description = """Create a new entangled project from a template.
13
11
 
@@ -42,61 +40,52 @@ def print_help() -> None:
42
40
  For this we first have to create a parser and attach this `new` function
43
41
  to it. Then we have a parser object that we can `print_help()` from.
44
42
  """
45
- parser = argparse.ArgumentParser(formatter_class=RichHelpFormatter)
46
- argh.add_commands(parser, [new], func_kwargs={"formatter_class": RichHelpFormatter})
47
- get_subparsers(parser).choices["new"].print_help()
43
+ ctx = click.get_current_context()
44
+ click.echo(ctx.get_help())
45
+ ctx.exit()
48
46
 
49
47
 
50
- @argh.arg(
51
- "-a",
52
- "--answers-file",
48
+ @main.command(
49
+ epilog="This command, and the options provided are a front-end for `copier`. " +
50
+ "See https://copier.readthedocs.io/en/stable/ for more information."
51
+ )
52
+ @click.option(
53
+ "-a", "--answers-file",
53
54
  help="Update using this path (relative to [project_path]) to find the answers file for `copier`",
54
55
  )
55
- @argh.arg(
56
- "-d",
57
- "--data",
56
+ @click.option(
57
+ "-d", "--data",
58
58
  help='"VARIABLE1=VALUE1;VARIABLE1=VALUE2" Make VARIABLEs available as VALUEs when rendering the template; make sure to use quotation marks for multiple variables/values',
59
59
  )
60
- @argh.arg(
61
- "-D",
62
- "--defaults",
60
+ @click.option(
61
+ "-D", "--defaults", is_flag=True,
63
62
  help="Use default answers to questions, which might be null if not specified; overwrites when combined with -d!",
64
63
  )
65
- @argh.arg(
66
- "-p",
67
- "--pretend",
64
+ @click.option(
65
+ "-p", "--pretend", is_flag=True,
68
66
  help="Run but do not make any changes",
69
67
  )
70
- @argh.arg(
71
- "-o",
72
- "--overwrite",
68
+ @click.option(
69
+ "-o", "--overwrite", is_flag=True,
73
70
  help="Overwrite files that already exist, without asking.",
74
71
  )
75
- @argh.arg(
76
- "-l",
77
- "--list-templates",
72
+ @click.option(
73
+ "-l", "--list-templates", is_flag=True,
78
74
  help="List all official templates and exit",
79
75
  )
80
- @argh.arg(
81
- "-t",
82
- "--trust",
76
+ @click.option(
77
+ "-t", "--trust", is_flag=True,
83
78
  help='Allow templates with unsafe features (Jinja extensions, migrations, tasks); "True" for officially supported templates',
84
79
  )
85
- @argh.arg(
86
- "template",
87
- # default=None,
88
- nargs="?",
89
- help="Template handle or URL; initialize a new project from this template",
80
+ @click.argument(
81
+ "template"
90
82
  )
91
- @argh.arg(
92
- "project-path",
93
- # default=None,
94
- nargs="?",
95
- help="Initialize a new project at this path",
83
+ @click.argument(
84
+ "project-path"
96
85
  )
97
86
  def new(
98
- template: str | None,
99
- project_path: Path | None, *,
87
+ template: str,
88
+ project_path: Path, *,
100
89
  answers_file: str | None = None,
101
90
  data: str = "",
102
91
  defaults: bool = False,
@@ -105,7 +94,12 @@ def new(
105
94
  list_templates: bool = False,
106
95
  trust: bool = False,
107
96
  ) -> None:
108
- """Create a new entangled project from a template."""
97
+ """Create a new entangled project from a template.
98
+
99
+ TEMPLATE Template handle or URL; initialize a new project from this template.
100
+
101
+ PROJECT_PATH Initialize a new project at this path.
102
+ """
109
103
 
110
104
  from ..config.templates import templates as AVAILABLE_TEMPLATES
111
105
  from ..config.templates import Template
@@ -6,57 +6,15 @@ This command will read the markdown sources, then pretend to be tangling
6
6
  without actually writing out to source files.
7
7
  """
8
8
 
9
- from ..io import TransactionMode, transaction
10
- from ..config import config, get_input_files
11
- from ..hooks import get_hooks
12
- from ..document import ReferenceMap
13
- from ..errors.user import UserError
14
-
15
- import logging
16
- from pathlib import Path
9
+ from ..io import TransactionMode
10
+ from .main import main
11
+ from .tangle import do_tangle
17
12
 
18
13
 
14
+ @main.command(short_help="Reset the file database.")
19
15
  def reset():
20
16
  """
21
- Resets the database. This performs a tangle without actually writing
22
- output to the files, but updating the database as if we were.
17
+ Resets the file database. This performs a tangle without actually
18
+ writing output to the files, but updating the database as if we were.
23
19
  """
24
- config.read()
25
-
26
- # these imports depend on config being read
27
- from ..markdown_reader import read_markdown_file
28
- from ..tangle import tangle_ref
29
-
30
- input_file_list = get_input_files()
31
-
32
- refs = ReferenceMap()
33
- hooks = get_hooks()
34
- logging.debug("tangling with hooks: %s", [h.__module__ for h in hooks])
35
- mode = TransactionMode.RESETDB
36
- annotation_method = config.get.annotation
37
-
38
- try:
39
- with transaction(mode) as t:
40
- for path in input_file_list:
41
- logging.debug("reading `%s`", path)
42
- t.update(path)
43
- _, _ = read_markdown_file(t, path, refs=refs, hooks=hooks)
44
-
45
- for h in hooks:
46
- h.pre_tangle(refs)
47
-
48
- for tgt in refs.targets:
49
- result, deps = tangle_ref(refs, tgt, annotation_method)
50
- mask = next(iter(refs.by_name(tgt))).mode
51
- t.write(Path(tgt), result, list(map(Path, deps)), mask)
52
-
53
- for h in hooks:
54
- h.on_tangle(t, refs)
55
-
56
- t.clear_orphans()
57
-
58
- for h in hooks:
59
- h.post_tangle(refs)
60
-
61
- except UserError as e:
62
- logging.error(str(e))
20
+ do_tangle(mode=TransactionMode.RESETDB)
@@ -1,7 +1,8 @@
1
1
  from __future__ import annotations
2
2
  from collections.abc import Iterable
3
- from ..status import list_input_files, list_dependent_files
4
- from ..config import config
3
+ from ..status import list_dependent_files
4
+ from ..config import Config, read_config, get_input_files
5
+ from ..io import FileCache
5
6
  from pathlib import Path
6
7
 
7
8
  from rich.console import Console, Group
@@ -10,6 +11,8 @@ from rich.table import Table
10
11
  from rich.panel import Panel
11
12
  from rich.tree import Tree
12
13
 
14
+ from .main import main
15
+
13
16
 
14
17
  def tree_from_files(files: Iterable[Path]):
15
18
  tree = Tree(label=".")
@@ -28,23 +31,25 @@ def files_panel(file_list: Iterable[Path], title: str) -> Panel:
28
31
 
29
32
 
30
33
  def rich_status():
34
+ fs = FileCache()
35
+ cfg = Config() | read_config(fs)
31
36
  config_table = Table()
32
37
  config_table.add_column("name")
33
38
  config_table.add_column("value")
34
39
  config_table.add_row(
35
- "Watch list", ", ".join(f"'{pat}'" for pat in config.get.watch_list)
40
+ "Watch list", ", ".join(f"'{pat}'" for pat in cfg.watch_list)
36
41
  )
37
42
  config_table.add_row(
38
- "Ignore list", ", ".join(f"'{pat}'" for pat in config.get.ignore_list)
43
+ "Ignore list", ", ".join(f"'{pat}'" for pat in cfg.ignore_list)
39
44
  )
40
- config_table.add_row("Hooks enabled", ", ".join(config.get.hooks))
45
+ config_table.add_row("Hooks enabled", ", ".join(cfg.hooks))
41
46
 
42
47
  console = Console(color_system="auto")
43
48
  group = Group(
44
49
  Panel(config_table, title="config", border_style="dark_cyan"),
45
50
  Columns(
46
51
  [
47
- files_panel(list_input_files(), "input files"),
52
+ files_panel(get_input_files(fs, cfg), "input files"),
48
53
  files_panel(list_dependent_files(), "dependent files"),
49
54
  ]
50
55
  ),
@@ -53,6 +58,7 @@ def rich_status():
53
58
  console.print(group)
54
59
 
55
60
 
61
+ @main.command()
56
62
  def status():
57
- config.read()
63
+ """Print a status overview."""
58
64
  rich_status()
@@ -1,41 +1,17 @@
1
- from itertools import chain
2
- from pathlib import Path
1
+ import rich_click as click
3
2
 
4
- import logging
5
- import argh # type: ignore
3
+ from .main import main
6
4
 
7
- from ..config import config
8
- from ..document import ReferenceMap, Content, PlainText, ReferenceId
5
+ from ..interface import Document
9
6
  from ..io import transaction, TransactionMode
10
7
  from ..errors.user import UserError
11
- from ..config import get_input_files
12
8
 
13
9
 
14
- def stitch_markdown(reference_map: ReferenceMap, content: list[Content]) -> str:
15
- def get(item: Content):
16
- match item:
17
- case PlainText(s):
18
- return s
19
- case ReferenceId():
20
- return reference_map.get_codeblock(item).indented_text
21
-
22
- return "\n".join(get(i) for i in content) + "\n"
23
-
24
-
25
- @argh.arg("--force", help="force overwrite on conflict")
26
- @argh.arg("-s", "--show", help="only show, don't act")
10
+ @main.command()
11
+ @click.option("-f", "--force", is_flag=True, help="force overwrite on conflict")
12
+ @click.option("-s", "--show", is_flag=True, help="only show, don't act")
27
13
  def stitch(*, force: bool = False, show: bool = False):
28
- """Stitch code changes back into the Markdown"""
29
- config.read()
30
-
31
- # these imports depend on config being read
32
- from ..markdown_reader import read_markdown_file
33
- from ..code_reader import CodeReader
34
- from ..hooks import get_hooks
35
-
36
- input_file_list = get_input_files()
37
- hooks = get_hooks()
38
-
14
+ """Stitch code changes back into the documentation."""
39
15
  if show:
40
16
  mode = TransactionMode.SHOW
41
17
  elif force:
@@ -43,22 +19,9 @@ def stitch(*, force: bool = False, show: bool = False):
43
19
  else:
44
20
  mode = TransactionMode.FAIL
45
21
 
46
- refs = ReferenceMap()
47
- content: dict[Path, list[Content]] = {}
48
- try:
49
- with transaction(mode) as t:
50
- for path in input_file_list:
51
- logging.debug("reading `%s`", path)
52
- _, c = read_markdown_file(t, path, refs=refs, hooks=hooks)
53
- content[path] = c
54
-
55
- for path in t.db.managed_files:
56
- logging.debug("reading `%s`", path)
57
- t.update(path)
58
- _ = CodeReader(path, refs).run(t.read(path))
59
-
60
- for path in input_file_list:
61
- t.write(path, stitch_markdown(refs, content[path]), [])
22
+ doc = Document()
62
23
 
63
- except UserError as e:
64
- logging.error(str(e))
24
+ with transaction(mode) as t:
25
+ doc.load(t)
26
+ doc.load_all_code(t)
27
+ doc.stitch(t)
@@ -1,51 +1,81 @@
1
- from typing import Optional, Callable
2
- from itertools import chain
3
- from pathlib import Path
1
+ from enum import Enum
4
2
 
5
- import logging
3
+ from ..io import filedb, FileCache, transaction
4
+ from ..interface import Document
5
+ from ..errors.user import UserError
6
+
7
+ from .main import main
6
8
 
7
- from ..io import filedb, FileCache
8
- from ..config import config
9
- from .stitch import stitch, get_input_files
10
- from .tangle import tangle
9
+ import logging
11
10
 
12
11
 
13
- def _stitch_then_tangle():
14
- stitch()
15
- tangle()
12
+ class Action(Enum):
13
+ NOTHING = 0
14
+ TANGLE = 1
15
+ STITCH = 2
16
16
 
17
17
 
18
- def sync_action() -> Callable[[], None] | None:
19
- input_file_list = get_input_files()
20
- fs = FileCache()
18
+ def sync_action(doc: Document) -> Action:
19
+ input_file_list = doc.input_files()
21
20
 
22
- with filedb(readonly=True) as db:
23
- changed = set(db.changed_files(fs))
21
+ with filedb(readonly=True, fs=doc.context.fs) as db:
22
+ changed = set(db.changed_files(doc.context.fs))
24
23
 
25
24
  if not all(f in db for f in input_file_list):
26
- return tangle
25
+ return Action.TANGLE
27
26
 
28
27
  if not changed:
29
- return None
28
+ return Action.NOTHING
30
29
 
31
30
  if changed.isdisjoint(db.managed_files):
32
- logging.info("Tangling")
33
- return tangle
31
+ return Action.TANGLE
34
32
 
35
33
  if changed.issubset(db.managed_files):
36
- logging.info("Stitching")
37
- return _stitch_then_tangle
34
+ return Action.STITCH
38
35
 
39
36
  logging.error("changed: %s", [str(p) for p in changed])
40
37
  logging.error(
41
38
  "Both markdown and code seem to have changed, don't know what to do now."
42
39
  )
43
- return None
40
+ return Action.NOTHING
41
+
42
+
43
+ def tangle(doc: Document):
44
+ with transaction() as t:
45
+ doc.load(t)
46
+ doc.tangle(t)
47
+ t.clear_orphans()
48
+ for h in doc.context.all_hooks:
49
+ h.post_tangle(doc.reference_map)
50
+
51
+
52
+ def stitch(doc: Document):
53
+ with transaction() as t:
54
+ doc.load(t)
55
+ doc.load_all_code(t)
56
+ doc.stitch(t)
57
+ with transaction() as t:
58
+ doc.tangle(t)
59
+ for h in doc.context.all_hooks:
60
+ h.post_tangle(doc.reference_map)
61
+
62
+
63
+ def run_sync():
64
+ doc = Document()
65
+ match sync_action(doc):
66
+ case Action.TANGLE:
67
+ logging.info("Tangling.")
68
+ tangle(doc)
69
+
70
+ case Action.STITCH:
71
+ logging.info("Stitching.")
72
+ stitch(doc)
73
+
74
+ case Action.NOTHING:
75
+ pass
44
76
 
45
77
 
78
+ @main.command()
46
79
  def sync():
47
80
  """Be smart wether to tangle or stich"""
48
- config.read()
49
- action = sync_action()
50
- if action is not None:
51
- action()
81
+ run_sync()
@@ -1,74 +1,48 @@
1
- from pathlib import Path
1
+ import rich_click as click
2
2
 
3
- import argh # type: ignore
4
- import logging
3
+ from .main import main
5
4
 
6
- from ..document import ReferenceMap
7
- from ..config import config, AnnotationMethod, get_input_files
8
- from ..io import transaction, TransactionMode
9
- from ..hooks import get_hooks
5
+ from ..config import AnnotationMethod, Config
6
+ from ..io import AbstractFileCache, FileCache, transaction, TransactionMode
10
7
  from ..errors.user import UserError
8
+ from ..interface import Context, Document
11
9
 
12
10
 
13
- @argh.arg(
14
- "-a",
15
- "--annotate",
16
- choices=[m.name.lower() for m in AnnotationMethod],
17
- help="annotation method",
18
- )
19
- @argh.arg("--force", help="force overwrite on conflict")
20
- @argh.arg("-s", "--show", help="only show, don't act")
21
- @argh.arg("-r", "--reset-db", help="resets database")
22
- def tangle(*, annotate: str | None = None, force: bool = False, show: bool = False, reset_db: bool = False):
23
- """Tangle codes from Markdown"""
24
- config.read()
25
-
26
- # these imports depend on config being read
27
- from ..markdown_reader import read_markdown_file
28
- from ..tangle import tangle_ref
29
-
30
- if annotate is None:
31
- annotation_method = config.get.annotation
32
- else:
33
- annotation_method = AnnotationMethod[annotate.upper()]
34
-
35
- input_file_list = get_input_files()
36
-
37
- refs = ReferenceMap()
38
- hooks = get_hooks()
39
- logging.debug("tangling with hooks: %s", [h.__module__ for h in hooks])
40
-
11
+ @main.command()
12
+ @click.option("-a", "--annotate", type=click.Choice(AnnotationMethod, case_sensitive=False),
13
+ help="annotation method")
14
+ @click.option("-f", "--force", is_flag=True, help="force overwriting existing files")
15
+ @click.option("-s", "--show", is_flag=True, help="only show what would happen")
16
+ def tangle(*, annotate: AnnotationMethod | None = None, force: bool = False, show: bool = False):
41
17
  if show:
42
18
  mode = TransactionMode.SHOW
43
- elif reset_db:
44
- mode = TransactionMode.RESETDB
45
19
  elif force:
46
20
  mode = TransactionMode.FORCE
47
21
  else:
48
22
  mode = TransactionMode.FAIL
49
23
 
50
- try:
51
- with transaction(mode) as t:
52
- for path in input_file_list:
53
- logging.debug("reading `%s`", path)
54
- t.update(path)
55
- _, _ = read_markdown_file(t, path, refs=refs, hooks=hooks)
24
+ do_tangle(annotate=annotate, mode=mode, skip_post_tangle=False)
25
+
56
26
 
57
- for h in hooks:
58
- h.pre_tangle(refs)
27
+ def do_tangle(*,
28
+ annotate: AnnotationMethod | None = None,
29
+ mode: TransactionMode = TransactionMode.FAIL,
30
+ fs: AbstractFileCache | None = None,
31
+ skip_post_tangle: bool = True):
32
+ """Tangle codes from the documentation."""
59
33
 
60
- for tgt in refs.targets:
61
- result, deps = tangle_ref(refs, tgt, annotation_method)
62
- mask = next(iter(refs.by_name(tgt))).mode
63
- t.write(Path(tgt), result, list(map(Path, deps)), mask)
34
+ if fs is None:
35
+ fs = FileCache()
64
36
 
65
- for h in hooks:
66
- h.on_tangle(t, refs)
37
+ doc = Document(context=Context(fs=fs))
67
38
 
68
- t.clear_orphans()
39
+ with transaction(mode, fs=fs) as t:
40
+ doc.load(t)
41
+ doc.tangle(t, annotate)
42
+ t.clear_orphans()
69
43
 
70
- for h in hooks:
71
- h.post_tangle(refs)
44
+ if skip_post_tangle:
45
+ return
72
46
 
73
- except UserError as e:
74
- logging.error(str(e))
47
+ for h in doc.context.all_hooks:
48
+ h.post_tangle(doc.reference_map)