@passioncode-ai/fabric-agent-adapter 0.80.2 → 0.81.0
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.
- package/CHANGELOG.md +79 -0
- package/README.md +13 -2
- package/SECURITY.md +23 -8
- package/bin/fabric-agent-adapter.js +24 -1
- package/package.json +1 -1
- package/plugins/fabric-agent-adapter/.claude-plugin/plugin.json +1 -1
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/SKILL.md +6 -4
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/dashboard-links.md +8 -3
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/references/source-and-instance.md +44 -7
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/adapt_project.py +107 -2
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/check_dashboard_link.py +14 -2
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_secrets.py +18 -3
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_source_check.py +55 -75
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_source_restart.py +56 -0
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/fabric_source_tree.py +912 -0
- package/plugins/fabric-agent-adapter/skills/adapting-projects-to-fabric/scripts/new_agent.py +308 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/SKILL.md +5 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/recipes/support-agent/README.md +1 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/dashboard-links.md +8 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/delegation.md +5 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/interop.md +28 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/kit-modules.md +3 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/lifecycle.md +25 -9
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/migrating-a-service.md +1 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/package.md +10 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/references/protocol.md +22 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_dashboard_layout.mjs +21 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_dashboard_link.py +14 -2
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service.py +17 -28
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service_channel_agent.py +2 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/check_service_rules.py +135 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-descriptor.mjs +78 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-holds.mjs +266 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-http.mjs +64 -5
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-package.mjs +98 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-peer-owner.mjs +3 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-protocols.mjs +1 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-secrets.mjs +10 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-service.mjs +5 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric-supervisors.mjs +76 -11
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_descriptor.py +85 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_holds.py +316 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_http.py +31 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_package.py +103 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_peer_owner.py +13 -8
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_protocols.py +1 -1
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_secrets.py +18 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_service.py +3 -0
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/fabric_supervisors.py +64 -3
- package/plugins/fabric-agent-adapter/skills/building-fabric-services/scripts/kit.json +141 -17
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/SKILL.md +16 -6
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/agent-templates.md +44 -0
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/communication-agent-template.md +19 -328
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/dashboard-links.md +8 -3
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/models-and-providers.md +1 -1
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/new-agent.md +42 -0
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/references/source-and-instance.md +44 -7
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/schemas/template/access.schema.json +50 -0
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/schemas/template/connections.schema.json +23 -0
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/schemas/template/data-rooms.schema.json +74 -0
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/schemas/template/manifest.schema.json +170 -0
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/schemas/template/template-header.schema.json +25 -0
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/scripts/check_dashboard_link.py +14 -2
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/scripts/template_check.py +491 -0
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/templates/communication-agent/CHANGELOG.md +7 -0
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/templates/communication-agent/TEMPLATE.md +347 -0
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/templates/communication-agent/access.json +24 -0
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/templates/communication-agent/assembly.md +15 -0
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/templates/communication-agent/checks/acceptance.md +17 -0
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/templates/communication-agent/connections.json +29 -0
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/templates/communication-agent/data-rooms.json +9 -0
- package/plugins/fabric-agent-adapter/skills/creating-fabric-agents/templates/communication-agent/manifest.json +32 -0
- package/plugins/fabric-agent-adapter/skills/observing-products/SKILL.md +1 -1
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/SKILL.md +1 -1
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/references/project-board.md +1 -1
- package/plugins/fabric-agent-adapter/skills/working-with-fabric/scripts/fabric_session_env.py +1 -1
|
@@ -44,6 +44,12 @@ shorter than three characters ignored, matched without regard to case. A list in
|
|
|
44
44
|
is refused: it would travel with the source. The summary says whether Git chose the files or every file was read.
|
|
45
45
|
Under a locale whose file-system encoding is not UTF-8 the check restarts itself in Python's UTF-8 mode, so a name,
|
|
46
46
|
an argument and a file are read in one encoding. Standard library only.
|
|
47
|
+
|
|
48
|
+
Under a source manifest (DEC-0149, FAA-261): when ROOT holds `fabric.source.json`, the manifest decides what is source
|
|
49
|
+
and the contract's tree check reads it (fabric_source_tree.py, held to the contract's vectors): blocking findings exit
|
|
50
|
+
1, warnings alone (`email`, `link-outside`, `no-license`, `unused-exclude`, `excluded-by-fold`) exit 0, a source file
|
|
51
|
+
not read exits 2; each finding names its path, line and column, never the text found. `.fabric-source-ignore` and
|
|
52
|
+
`--exclude` are not read there.
|
|
47
53
|
"""
|
|
48
54
|
# Example URLs write their scheme in angle brackets: read `<https>` as `https` (FAA-212).
|
|
49
55
|
|
|
@@ -65,6 +71,28 @@ from urllib.parse import unquote
|
|
|
65
71
|
from typing import Dict, Iterable, List, NoReturn, Optional, Set, Tuple
|
|
66
72
|
|
|
67
73
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _refuse_sibling(step: str, missing: BaseException) -> NoReturn:
|
|
77
|
+
"""A sibling module that did not load: name it and exit 2, never a traceback and never exit 1 (a finding)."""
|
|
78
|
+
# the exception's own text is never printed: an OSError's carries a path, which may be under the owner's home
|
|
79
|
+
why = "a module beside this file refused to load"
|
|
80
|
+
try:
|
|
81
|
+
here, sibling = Path(__file__).resolve().parent, step + ".py"
|
|
82
|
+
file = getattr(missing, "filename", None) if isinstance(missing, OSError) else None
|
|
83
|
+
why = ("%s must sit beside this file" % sibling if not (here / sibling).is_file()
|
|
84
|
+
else "%s beside this file cannot be read" % sibling
|
|
85
|
+
if isinstance(file, (str, bytes)) and Path(os.fsdecode(file)).resolve() == here / sibling
|
|
86
|
+
else "%s beside this file is from another version: copy it with this one" % sibling
|
|
87
|
+
if isinstance(missing, (ImportError, SyntaxError))
|
|
88
|
+
else "%s beside this file refused to load (%s): copy the scripts folder whole, from one version"
|
|
89
|
+
% (sibling, type(missing).__name__))
|
|
90
|
+
except Exception: # naming the cause must never be what fails
|
|
91
|
+
pass
|
|
92
|
+
sys.stderr.write("fabric_source_check.py: %s\n" % why)
|
|
93
|
+
sys.exit(2)
|
|
94
|
+
|
|
95
|
+
|
|
68
96
|
_WROTE_BYTECODE, sys.dont_write_bytecode = sys.dont_write_bytecode, True # a copy inside the checked tree adds no .pyc
|
|
69
97
|
_STEP = "fabric_secrets" # the sibling being loaded, so a refusal names it whatever the exception says
|
|
70
98
|
try:
|
|
@@ -72,26 +100,13 @@ try:
|
|
|
72
100
|
_STEP = "fabric_source_ignore"
|
|
73
101
|
from fabric_source_ignore import ( # noqa: E402,F401 - how the ignore lines are read, beside this file
|
|
74
102
|
IGNORE_FILE, CheckError, _as_bytes, _glob, _ignored, _line, _readable_glob, ignore_globs)
|
|
103
|
+
_STEP = "fabric_source_restart"
|
|
104
|
+
from fabric_source_restart import _interpreter, _interpreter_flags, _keep_console, _utf8_restart # noqa: E402,F401
|
|
75
105
|
_STEP = "fabric_source_homes"
|
|
76
106
|
from fabric_source_homes import ( # noqa: E402,F401 - how a declared home is read, beside this file
|
|
77
107
|
HOME_ENTRY, _NAME_JOIN, _NAME_START, _SEP, _dashed_home, _name_parts, _owner_homes, _spellings)
|
|
78
108
|
except Exception as _missing: # a copy without its siblings, or mixed from two versions: a refusal, never exit 1
|
|
79
|
-
|
|
80
|
-
_why = "a module beside this file refused to load"
|
|
81
|
-
try:
|
|
82
|
-
_HERE, _sibling = Path(__file__).resolve().parent, _STEP + ".py"
|
|
83
|
-
_file = getattr(_missing, "filename", None) if isinstance(_missing, OSError) else None
|
|
84
|
-
_why = ("%s must sit beside this file" % _sibling if not (_HERE / _sibling).is_file()
|
|
85
|
-
else "%s beside this file cannot be read" % _sibling
|
|
86
|
-
if isinstance(_file, (str, bytes)) and Path(os.fsdecode(_file)).resolve() == _HERE / _sibling
|
|
87
|
-
else "%s beside this file is from another version: copy it with this one" % _sibling
|
|
88
|
-
if isinstance(_missing, (ImportError, SyntaxError))
|
|
89
|
-
else "%s beside this file refused to load (%s): copy the scripts folder whole, from one version"
|
|
90
|
-
% (_sibling, type(_missing).__name__))
|
|
91
|
-
except Exception: # naming the cause must never be what fails
|
|
92
|
-
pass
|
|
93
|
-
sys.stderr.write("fabric_source_check.py: %s\n" % _why)
|
|
94
|
-
sys.exit(2)
|
|
109
|
+
_refuse_sibling(_STEP, _missing)
|
|
95
110
|
finally:
|
|
96
111
|
sys.dont_write_bytecode = _WROTE_BYTECODE
|
|
97
112
|
|
|
@@ -127,13 +142,16 @@ PRIVATE_KEY = re.compile(r"-----BEGIN (?:[A-Z0-9]+ )*PRIVATE KEY(?: BLOCK)?-----
|
|
|
127
142
|
JWK_PRIVATE = re.compile(r"\\?[\"']d\\?[\"'][ \t]*:[ \t]*\\?[\"']([A-Za-z0-9_-]{43,})") # JSON, escaped, Python
|
|
128
143
|
#: Words a documented or synthetic credential carries (AWS's `...EXAMPLE`, `sk-test-not-a-key`); a random key does not.
|
|
129
144
|
SYNTHETIC = ("example", "not-a-key", "notakey", "fake", "dummy", "placeholder", "redacted")
|
|
130
|
-
#
|
|
131
|
-
#
|
|
132
|
-
#
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
145
|
+
# This check's own key shapes, the detector's before DEC-0151 (unbounded, ASCII case-insensitive), apart from the shared
|
|
146
|
+
# SECRET_VALUE for parity with the contract's tree check (KEY_SHAPE_SOURCES, `src/source-rules.ts` at the pin; CT-207,
|
|
147
|
+
# FAA-258). Not the detector's `^bearer\s.+`, which reads a whole header value and would take prose that starts with the
|
|
148
|
+
# word: a bearer token is looked for as `_BEARER_INSIDE` does, the word, then a run of 12 or more with a digit.
|
|
149
|
+
KEY_SHAPE_SOURCES = ("sk-[A-Za-z0-9_-]{16,}", "gh[pousr]_[A-Za-z0-9]{20,}", "xox[abposr]-[A-Za-z0-9-]{10,}", "AKIA[0-9A-Z]{16}", "glpat-[A-Za-z0-9_-]{20,}")
|
|
150
|
+
PROVIDER_KEY = re.compile("(" + "|".join(KEY_SHAPE_SOURCES) + ")", re.IGNORECASE | re.ASCII)
|
|
151
|
+
#: A URL parameter's value read as the contract's tree check reads it (`URL_SECRET_VALUE` in `src/source-rules.ts`): the
|
|
152
|
+
#: detector's whole-value `^bearer` branch and this check's own unbounded shapes, never the shared DEC-0151 detector.
|
|
153
|
+
URL_SECRET_VALUE = re.compile("(^bearer[" + _secrets.JS_SPACE + "]" + _secrets._NOT_LINE_END + "+|" + "|".join(KEY_SHAPE_SOURCES) + ")",
|
|
154
|
+
re.IGNORECASE | re.ASCII)
|
|
137
155
|
#: Files that are credentials by their name: pack's own rule (fabric_package.SECRET_FILE and SECRET_FILE_OK, held
|
|
138
156
|
#: equal by test_source_check), and the kit's token files (`service.token`, fabric_auth; `board.credential`, fabric_hub).
|
|
139
157
|
_JS_DOT = "[^\n\r\u2028\u2029]"
|
|
@@ -216,7 +234,7 @@ def _url_secret(url: str, depth: int = 0) -> bool:
|
|
|
216
234
|
if not _real(value):
|
|
217
235
|
continue
|
|
218
236
|
if (_secrets.credential_name(name) and _secrets._long_secret(value)) or \
|
|
219
|
-
_secrets.JWT_SHAPE.search(value) or
|
|
237
|
+
_secrets.JWT_SHAPE.search(value) or URL_SECRET_VALUE.search(value):
|
|
220
238
|
return True
|
|
221
239
|
return False
|
|
222
240
|
|
|
@@ -399,13 +417,13 @@ def _instance_finding(text: str, entries: List[str]) -> Optional[str]:
|
|
|
399
417
|
|
|
400
418
|
|
|
401
419
|
def _alternatives(pattern: "re.Pattern[str]") -> List[Tuple["re.Pattern[str]", ...]]:
|
|
402
|
-
"""Each provider-key alternative of
|
|
420
|
+
"""Each provider-key alternative of this check's pattern as (its starts, its prefix, a run of its class, least
|
|
403
421
|
length, open-ended, the whole alternative), so a key is read where its own class ends, not where another's does."""
|
|
404
422
|
found = []
|
|
405
423
|
for alt in pattern.pattern[1:-1].split("|"):
|
|
406
424
|
part = re.fullmatch(r"(.+?)(\[[^\]]+\])\{(\d+)(,?)\}", alt)
|
|
407
425
|
if part is None: # not an assert: `python -O` would drop it
|
|
408
|
-
raise ImportError("
|
|
426
|
+
raise ImportError("KEY_SHAPE_SOURCES has a key shape this check cannot read: %s" % alt[:12])
|
|
409
427
|
prefix, cls, least, open_ended = part.groups()
|
|
410
428
|
found.append((re.compile("(?=%s)" % prefix, pattern.flags), re.compile(prefix, pattern.flags),
|
|
411
429
|
re.compile(cls + "+", pattern.flags), int(least), bool(open_ended), re.compile(alt, pattern.flags)))
|
|
@@ -714,6 +732,15 @@ def check(root: Path, entries: List[str], globs: List[str], every_file: bool = F
|
|
|
714
732
|
files, mode = _walk_files(root, unreadable, PACK_REFUSES), "walk"
|
|
715
733
|
elif not files: # not a checkout, Git refused it, or a folder Git ignores or holds nothing of: read what is there
|
|
716
734
|
files, mode = _walk_files(root, unreadable, WALK_SKIP, dependencies), "walk"
|
|
735
|
+
if os.path.lexists(str(root / "fabric.source.json")): # DEC-0149: the manifest decides (fabric_source_tree.py)
|
|
736
|
+
wrote, sys.dont_write_bytecode = sys.dont_write_bytecode, True # a copy inside the tree adds no .pyc
|
|
737
|
+
try:
|
|
738
|
+
import fabric_source_tree # noqa: E402 - beside this file; imported here, since it imports this one
|
|
739
|
+
except Exception as missing: # the same named-sibling refusal as the imports at the top (exit 2)
|
|
740
|
+
_refuse_sibling("fabric_source_tree", missing)
|
|
741
|
+
finally:
|
|
742
|
+
sys.dont_write_bytecode = wrote
|
|
743
|
+
return fabric_source_tree.check_folder(root, files, mode, entries, unreadable, dependencies)
|
|
717
744
|
findings: List[Dict[str, object]] = []
|
|
718
745
|
read = 0
|
|
719
746
|
skipped = {"binary": 0, "unreadable": len(unreadable), "excluded": 0, "nested": 0, "dependencies": len(dependencies)}
|
|
@@ -892,6 +919,8 @@ def run(argv: Optional[List[str]] = None) -> int:
|
|
|
892
919
|
except CheckError as exc:
|
|
893
920
|
print("fabric_source_check.py: %s" % exc, file=sys.stderr)
|
|
894
921
|
return 2
|
|
922
|
+
if result.get("manifest"): # under fabric.source.json: its own report (fabric_source_tree.print_run)
|
|
923
|
+
return sys.modules["fabric_source_tree"].print_run(result, entries, list_path is not None, args.json, _print)
|
|
895
924
|
findings = result["findings"]
|
|
896
925
|
skipped = result["skipped"]
|
|
897
926
|
listed = sum(1 for e in entries if e and not e.startswith(HOME_ENTRY))
|
|
@@ -926,55 +955,6 @@ def run(argv: Optional[List[str]] = None) -> int:
|
|
|
926
955
|
return 0 if complete else 2
|
|
927
956
|
|
|
928
957
|
|
|
929
|
-
def _utf8_restart(encoding: str, utf8_mode: int, executable: str, argv: List[str]) -> Optional[List[bytes]]:
|
|
930
|
-
"""The command that restarts the check in Python's UTF-8 mode, or None when it needs none. Under a locale whose
|
|
931
|
-
file-system encoding is not UTF-8 (ISO-8859-1, KOI8-R, a C locale with UTF-8 mode off), a name, an argument and a
|
|
932
|
-
file's text would each be read in its own encoding; in UTF-8 mode all three are, and a byte that is not UTF-8 stays
|
|
933
|
-
that byte. Windows names are UTF-16 and need none. The arguments go back as the bytes they came in. An empty list:
|
|
934
|
-
a restart is needed and no interpreter is known (the caller exits 2 rather than read in two encodings)."""
|
|
935
|
-
if os.name == "nt" or utf8_mode or encoding.lower().replace("_", "-") in ("utf-8", "utf8"):
|
|
936
|
-
return None
|
|
937
|
-
if not executable:
|
|
938
|
-
return []
|
|
939
|
-
return [os.fsencode(executable), b"-X", b"utf8"] + [os.fsencode(a) for a in argv]
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
def _interpreter() -> str:
|
|
943
|
-
"""The running interpreter: Linux's `/proc/self/exe`, which no PATH or working folder can stand in for (Python 3.9
|
|
944
|
-
may take a `./python3` of the working folder as sys.executable), else sys.executable, which may be empty."""
|
|
945
|
-
try:
|
|
946
|
-
return os.readlink("/proc/self/exe")
|
|
947
|
-
except (OSError, AttributeError):
|
|
948
|
-
return sys.executable or ""
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
def _interpreter_flags(flags: object, xoptions: Dict[str, object]) -> List[str]:
|
|
952
|
-
"""The interpreter's own options, rebuilt where sys.orig_argv is missing (Python 3.9), so a restart keeps `-I`."""
|
|
953
|
-
out: List[str] = []
|
|
954
|
-
if getattr(flags, "isolated", 0):
|
|
955
|
-
out.append("-I")
|
|
956
|
-
else:
|
|
957
|
-
out += ["-E"] * bool(getattr(flags, "ignore_environment", 0)) + ["-s"] * bool(getattr(flags, "no_user_site", 0))
|
|
958
|
-
out += ["-S"] * bool(getattr(flags, "no_site", 0)) + ["-B"] * bool(getattr(flags, "dont_write_bytecode", 0))
|
|
959
|
-
if getattr(flags, "optimize", 0):
|
|
960
|
-
out.append("-" + "O" * int(getattr(flags, "optimize")))
|
|
961
|
-
for key, value in xoptions.items():
|
|
962
|
-
if key not in ("utf8", "fabric_console"):
|
|
963
|
-
out += ["-X", key if value is True else "%s=%s" % (key, value)]
|
|
964
|
-
return out
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
def _keep_console(xoptions: Dict[str, object]) -> None:
|
|
968
|
-
"""After a restart, write the console's own encoding again (the parent named it in `-X fabric_console=`)."""
|
|
969
|
-
console = xoptions.get("fabric_console")
|
|
970
|
-
if isinstance(console, str):
|
|
971
|
-
for stream in (sys.stdout, sys.stderr):
|
|
972
|
-
try:
|
|
973
|
-
stream.reconfigure(encoding=console, errors="backslashreplace") # type: ignore[union-attr]
|
|
974
|
-
except (AttributeError, LookupError, ValueError):
|
|
975
|
-
pass
|
|
976
|
-
|
|
977
|
-
|
|
978
958
|
if __name__ == "__main__":
|
|
979
959
|
_keep_console(getattr(sys, "_xoptions", {}))
|
|
980
960
|
_argv = (list(sys.orig_argv[1:]) if hasattr(sys, "orig_argv")
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""How fabric_source_check.py restarts itself in Python's UTF-8 mode under a locale whose file-system encoding is not
|
|
2
|
+
UTF-8, and keeps the console's encoding and the interpreter's own options across the restart. Standard library only."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
from typing import Dict, List, Optional
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _utf8_restart(encoding: str, utf8_mode: int, executable: str, argv: List[str]) -> Optional[List[bytes]]:
|
|
11
|
+
"""The command that restarts the check in Python's UTF-8 mode, or None when it needs none. Under a locale whose
|
|
12
|
+
file-system encoding is not UTF-8 (ISO-8859-1, KOI8-R, a C locale with UTF-8 mode off), a name, an argument and a
|
|
13
|
+
file's text would each be read in its own encoding; in UTF-8 mode all three are, and a byte that is not UTF-8 stays
|
|
14
|
+
that byte. Windows names are UTF-16 and need none. The arguments go back as the bytes they came in. An empty list:
|
|
15
|
+
a restart is needed and no interpreter is known (the caller exits 2 rather than read in two encodings)."""
|
|
16
|
+
if os.name == "nt" or utf8_mode or encoding.lower().replace("_", "-") in ("utf-8", "utf8"):
|
|
17
|
+
return None
|
|
18
|
+
if not executable:
|
|
19
|
+
return []
|
|
20
|
+
return [os.fsencode(executable), b"-X", b"utf8"] + [os.fsencode(a) for a in argv]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _interpreter() -> str:
|
|
24
|
+
"""The running interpreter: Linux's `/proc/self/exe`, which no PATH or working folder can stand in for (Python 3.9
|
|
25
|
+
may take a `./python3` of the working folder as sys.executable), else sys.executable, which may be empty."""
|
|
26
|
+
try:
|
|
27
|
+
return os.readlink("/proc/self/exe")
|
|
28
|
+
except (OSError, AttributeError):
|
|
29
|
+
return sys.executable or ""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _interpreter_flags(flags: object, xoptions: Dict[str, object]) -> List[str]:
|
|
33
|
+
"""The interpreter's own options, rebuilt where sys.orig_argv is missing (Python 3.9), so a restart keeps `-I`."""
|
|
34
|
+
out: List[str] = []
|
|
35
|
+
if getattr(flags, "isolated", 0):
|
|
36
|
+
out.append("-I")
|
|
37
|
+
else:
|
|
38
|
+
out += ["-E"] * bool(getattr(flags, "ignore_environment", 0)) + ["-s"] * bool(getattr(flags, "no_user_site", 0))
|
|
39
|
+
out += ["-S"] * bool(getattr(flags, "no_site", 0)) + ["-B"] * bool(getattr(flags, "dont_write_bytecode", 0))
|
|
40
|
+
if getattr(flags, "optimize", 0):
|
|
41
|
+
out.append("-" + "O" * int(getattr(flags, "optimize")))
|
|
42
|
+
for key, value in xoptions.items():
|
|
43
|
+
if key not in ("utf8", "fabric_console"):
|
|
44
|
+
out += ["-X", key if value is True else "%s=%s" % (key, value)]
|
|
45
|
+
return out
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _keep_console(xoptions: Dict[str, object]) -> None:
|
|
49
|
+
"""After a restart, write the console's own encoding again (the parent named it in `-X fabric_console=`)."""
|
|
50
|
+
console = xoptions.get("fabric_console")
|
|
51
|
+
if isinstance(console, str):
|
|
52
|
+
for stream in (sys.stdout, sys.stderr):
|
|
53
|
+
try:
|
|
54
|
+
stream.reconfigure(encoding=console, errors="backslashreplace") # type: ignore[union-attr]
|
|
55
|
+
except (AttributeError, LookupError, ValueError):
|
|
56
|
+
pass
|