sift-cli 1.0.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.
sift/digest.py ADDED
@@ -0,0 +1,101 @@
1
+ """A file someone already has, read for what happened in it.
2
+
3
+ Three questions now go to the same machinery. `distill` asks what mattered in a
4
+ command this tool just ran. `outline` asks what a source file declares. This
5
+ asks the third, and it is the one a caller reaches for most often without
6
+ noticing: *here is a file I did not produce and cannot afford to read whole --
7
+ what is in it?*
8
+
9
+ A build log written by CI last night. A test report attached to an issue. A
10
+ crash dump, a 40,000-line JSON export, the output of something that ran on
11
+ another machine entirely. None of these can be `run` again, and none of them are
12
+ source code, so neither of the first two questions fits.
13
+
14
+ The distinction from `outline` is not the file, it is the question. Asked of a
15
+ log, *what does this declare* returns nothing useful; asked of a Python module,
16
+ *what happened here* returns its imports. The caller knows which they are
17
+ holding, and the command word is where they say so -- exactly as it is for `run`
18
+ and `outline`.
19
+
20
+ What does not change: the file is opened here and its contents never enter the
21
+ conversation, the model answers with line numbers only, and every line shown is
22
+ printed from the file byte for byte. A 40,000-line log costs a screenful, and
23
+ the other 39,900 lines are one `sift peek` away because nothing was touched.
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ from pathlib import Path
29
+
30
+ from sift import lines as text_lines
31
+ from sift import records
32
+ from sift.distill import BUDGET, RECORDS, View, select
33
+ from sift.fallback import from_lines
34
+ from sift.model import Bridge
35
+ from sift.outline import text_of
36
+
37
+ QUESTION = (
38
+ "You are given a file, numbered by line. It is something that was already "
39
+ "produced and saved -- a log, a build or test transcript, a crash dump, a "
40
+ "report, an export -- and it is too long to read whole.\n"
41
+ "Choose the lines someone would need to understand what is in it: what "
42
+ "failed, what warned, what changed, what it concluded, the headings and "
43
+ "boundaries that say where one part ends and the next begins, and the few "
44
+ "lines around those that make them readable.\n"
45
+ "Leave out repetition, progress that only says work happened, and lines that "
46
+ "carry no information on their own.\n"
47
+ "Nothing here is guaranteed to be at the end: a saved file may stop in the "
48
+ "middle of whatever was writing it, so do not assume the last lines are the "
49
+ "conclusion.\n"
50
+ )
51
+
52
+
53
+ def digest(
54
+ path: str | Path,
55
+ bridge: Bridge | None = None,
56
+ budget: int | None = None,
57
+ keep: str | None = None,
58
+ ) -> View | None:
59
+ """What is in `path`, chosen by a model and printed from the file.
60
+
61
+ Handled by the path, like an outline, so the gap marker names the way back
62
+ and `sift peek` takes it unchanged. Returns nothing when there was no usable
63
+ judgement -- the same contract `distill` and `outline` keep, for the same
64
+ reason: a view that pretended to have been chosen would be worse than one
65
+ that admits it was not.
66
+ """
67
+ text = text_of(path)
68
+ found = records.of(text)
69
+ ceiling = BUDGET if budget is None else budget
70
+ if found is not None:
71
+ return select(
72
+ found,
73
+ RECORDS,
74
+ str(path),
75
+ bridge,
76
+ budget=ceiling,
77
+ keep=keep,
78
+ unit="record",
79
+ )
80
+ return select(
81
+ text_lines.of(text),
82
+ QUESTION,
83
+ str(path),
84
+ bridge,
85
+ budget=ceiling,
86
+ keep=keep,
87
+ )
88
+
89
+
90
+ def ends_of(path: str | Path) -> View:
91
+ """What to show when nobody could be asked.
92
+
93
+ The beginning of a log says what was being attempted and the end says how it
94
+ came out, which is a better guess here than it is for source code and still
95
+ only a guess. Every line it skips is counted and named.
96
+ """
97
+ text = text_of(path)
98
+ found = records.of(text)
99
+ if found is not None:
100
+ return from_lines(found, str(path), unit="record")
101
+ return from_lines(text_lines.of(text), str(path))