hill 0.2.0__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.
hill/__init__.py ADDED
@@ -0,0 +1,164 @@
1
+ """palace: a notes screen for a Markdown vault, with micro for editing. Its
2
+ settings are in hill-ops's strip, under it."""
3
+
4
+ import os
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ USAGE = """\
9
+ usage: hill [FOLDER] the notes screen, for FOLDER, else the folder
10
+ chosen on the first start
11
+ hill --resume [FOLDER] pick which of hill-ops's instances to start in
12
+ hill --new [FOLDER] in a new instance
13
+
14
+ palace is published as hill, and runs as `hill` or `palace`.
15
+
16
+ palace starts itself inside hill-ops, whose strip under it holds its settings,
17
+ (hill-ops, which it brings along), when it runs in a terminal;
18
+ PALACE_NO_HILL=1 keeps it on its own. hill-ops keeps an instance for each
19
+ palace, with its place, its panes and their widths, and starts the last one
20
+ that isn't running unless told otherwise. `hill-ops settings` shows the
21
+ settings on their own.
22
+ """
23
+
24
+
25
+ HILL_OPS = [sys.executable, "-m", "hill_ops"]
26
+ """hill-ops's command, from palace's own environment, where palace's
27
+ package brings it: `uv tool install` puts only palace's commands on PATH."""
28
+
29
+ LOOP = [str(Path(sys.executable).with_name("palace")), "_loop"]
30
+ """palace-loop, as palace starts it inside hill-ops: through its `palace`
31
+ command, beside the Python that runs it, so that hill-ops names the
32
+ program palace, as its instances and event log did when the import was
33
+ palace (work item 018 in hill-ops)."""
34
+
35
+ AGAIN = "PALACE_HILL_INSTANCE"
36
+ """A file, set by the palace that starts hill-ops, where the palace inside
37
+ writes hill-ops's instance, to start hill-ops again in the same one."""
38
+
39
+
40
+ def into_hill(args: list[str], choice: str | None = None) -> None:
41
+ """Start palace again inside hill-ops, under palace-loop, unless it's there
42
+ already, it isn't in a terminal, hill-ops isn't installed, or PALACE_NO_HILL
43
+ is set; and start hill-ops again, in the same instance, each time palace
44
+ asks to restart it all. `choice` ("resume" or "new") says which
45
+ instance to start in."""
46
+ if os.environ.get("HILL_SOCKET"):
47
+ if (note := os.environ.get(AGAIN)) and (instance := os.environ.get("HILL_INSTANCE")):
48
+ try:
49
+ Path(note).write_text(Path(instance).name)
50
+ except OSError:
51
+ pass
52
+ return
53
+ if os.environ.get("PALACE_NO_HILL"):
54
+ return
55
+ if not (sys.stdin.isatty() and sys.stdout.isatty()):
56
+ return
57
+ if _hill_installed():
58
+ import subprocess
59
+ import tempfile
60
+
61
+ from .restart import RESTART_ALL
62
+
63
+ handle, name = tempfile.mkstemp(prefix="palace-instance-")
64
+ os.close(handle)
65
+ env = os.environ | {AGAIN: name}
66
+ flags = [f"--{choice}"] if choice else []
67
+ try:
68
+ while (status := subprocess.call([*HILL_OPS, *flags, "--", *LOOP, *args], env=env)) == RESTART_ALL:
69
+ instance = Path(name).read_text().strip()
70
+ flags = ["--as", instance] if instance else []
71
+ finally:
72
+ Path(name).unlink(missing_ok=True)
73
+ sys.exit(status)
74
+
75
+
76
+ def _hill_installed() -> bool:
77
+ import importlib.util
78
+
79
+ return importlib.util.find_spec("hill_ops") is not None
80
+
81
+
82
+ def _report_to_hill(errors: list[str]) -> None:
83
+ """Tell hill-ops's event log what went wrong in the sync. It runs once
84
+ palace has left hill-ops's channel, so palace says hello again to do so."""
85
+ import asyncio
86
+
87
+ from hill_client import SOCKET, connect, line
88
+
89
+ if not (path := os.environ.get(SOCKET)):
90
+ return
91
+
92
+ async def tell() -> None:
93
+ _, writer = await connect(path, wait=0)
94
+ writer.write(line("hello", app="palace") + b"".join(line("error", what=what) for what in errors))
95
+ await writer.drain()
96
+ writer.close()
97
+ await writer.wait_closed()
98
+
99
+ try:
100
+ asyncio.run(tell())
101
+ except OSError:
102
+ pass # hill-ops has gone: nothing to tell
103
+
104
+
105
+ def main() -> None:
106
+ args = sys.argv[1:]
107
+ if args in (["-h"], ["--help"]):
108
+ print(USAGE, end="")
109
+ elif args[:1] == ["_loop"] and len(args) <= 2:
110
+ # palace-loop, as hill-ops runs it (see restart.py).
111
+ from .restart import loop
112
+
113
+ loop(args[1:])
114
+ elif len(args) == 2 and args[0] == "_preview":
115
+ # The preview's pane, beside palace inside hill-ops (see side.py).
116
+ from .side import main as side
117
+
118
+ side("preview", args[1])
119
+ elif len(args) >= 5 and args[0] == "_claude":
120
+ # A Claude session's pane, beside palace inside hill-ops (see claude.py).
121
+ from .claude import run_pane
122
+
123
+ done = args[4] == "--done"
124
+ mirror = args[5] if args[4] == "--mirror" and len(args) > 5 else None
125
+ sys.exit(run_pane(args[1], args[2], args[3], args[4 + done + 2 * bool(mirror):], done=done, mirror=mirror))
126
+ elif args == ["_hook"]:
127
+ # A Claude Code hook, in a session palace started (see claude.py).
128
+ from .claude import hook
129
+
130
+ hook()
131
+ elif len(args) <= 1 or (args[0] in ("--resume", "--new") and len(args) <= 2):
132
+ choice = args[0][2:] if args[:1] in (["--resume"], ["--new"]) else None
133
+ args = args[1:] if choice else args
134
+ folder = Path(args[0]).expanduser() if args else None
135
+ if folder is not None and not folder.is_dir():
136
+ print(f"palace: {folder} isn't a folder", file=sys.stderr)
137
+ sys.exit(2)
138
+ if folder is None:
139
+ from .config import ask_folder
140
+
141
+ ask_folder()
142
+ into_hill(args, choice)
143
+ from .app import PalaceApp
144
+ from .restart import RESTART, RESTART_ALL, again
145
+ from .sync import sync_vault
146
+
147
+ app = PalaceApp(folder)
148
+ app.run()
149
+ if app.return_code in (RESTART, RESTART_ALL):
150
+ again(app.return_code) # the sync waits for the last quit
151
+ errors: list[str] = []
152
+ report = sync_vault(
153
+ app.vault.root,
154
+ commit=app.pref(("sync", "commit"), True),
155
+ push=app.pref(("sync", "push"), True),
156
+ failed=errors.append,
157
+ )
158
+ if report:
159
+ print(report)
160
+ if errors:
161
+ _report_to_hill(errors)
162
+ else:
163
+ print(USAGE, end="", file=sys.stderr)
164
+ sys.exit(2)
hill/__main__.py ADDED
@@ -0,0 +1,3 @@
1
+ from . import main
2
+
3
+ main()