repocxt 0.1.0__tar.gz

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.
@@ -0,0 +1,28 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.0] - 2026-11-15
9
+
10
+ ### Added
11
+
12
+ - `ctx` CLI with a `root` argument (defaults to the current directory) and an
13
+ `-o/--output` flag for writing the context file instead of stdout.
14
+ - Repository walker that honors `.gitignore` and `.ctxignore` at every level of
15
+ the tree, following git semantics, and always skips `.git`, `.svn`, `.hg`,
16
+ `.ctx`, `__pycache__`, and `.mypy_cache`.
17
+ - `--include` and `--exclude` filter flags using pathspec glob syntax
18
+ (`exclude` wins over `include`), plus `--max-size` to skip large files.
19
+ - Binary-file detection via NUL-byte sniffing; binary files are listed in the
20
+ context document but their content is omitted.
21
+ - Token estimation with no external tokenizer dependency; accurate enough to
22
+ budget context window fits and slightly conservative.
23
+ - Markdown renderer emitting a directory tree, a files/bytes/tokens summary,
24
+ and one fenced block per source file with a language hint.
25
+ - `--max-tokens` to stop early once the running token estimate exceeds a limit,
26
+ `--no-tree` to omit the ASCII tree, and `--json` for a machine-readable
27
+ summary.
28
+ - `python -m ctx` support, `ctx --version`, and a `py.typed` marker.
repocxt-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kamran
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,4 @@
1
+ include LICENSE
2
+ include README.md
3
+ include CHANGELOG.md
4
+ recursive-include tests *.py
repocxt-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,218 @@
1
+ Metadata-Version: 2.4
2
+ Name: repocxt
3
+ Version: 0.1.0
4
+ Summary: Package a repository into a single, LLM-ready context file with a tree, file contents, and a token estimate.
5
+ Author: Kamran
6
+ License-Expression: MIT
7
+ Keywords: llm,context,cli,code,ai,prompt
8
+ Classifier: Environment :: Console
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Topic :: Software Development :: Libraries
16
+ Classifier: Topic :: Software Development :: Build Tools
17
+ Requires-Python: >=3.10
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: pathspec>=1.1
21
+ Provides-Extra: dev
22
+ Requires-Dist: pytest>=7.4; extra == "dev"
23
+ Requires-Dist: pytest-cov>=4.1; extra == "dev"
24
+ Requires-Dist: ruff>=0.4; extra == "dev"
25
+ Provides-Extra: build
26
+ Requires-Dist: build>=1.0; extra == "build"
27
+ Requires-Dist: twine>=4.0; extra == "build"
28
+ Dynamic: license-file
29
+
30
+ # ctx
31
+
32
+ Package a repository into a single, LLM-ready context file.
33
+
34
+ `ctx` walks a directory and emits one markdown file containing an ASCII tree of
35
+ the repo, the contents of every source file (fenced with language hints), and a
36
+ token estimate — so you can paste a whole repository into an LLM prompt, or
37
+ point an agent at it, and the model sees structure and code together.
38
+
39
+ ## What it looks like
40
+
41
+ Running `ctx` on this repo (tree trimmed for the README; the header counts are real):
42
+
43
+ ````text
44
+ # repo — context
45
+
46
+ - files: **26** · **67763 bytes** · ~**19876 tokens**
47
+
48
+ ## Tree
49
+
50
+ ```text
51
+ ├── .github/
52
+ │ ├── workflows/
53
+ │ │ ├── ci.yml
54
+ │ │ └── release.yml
55
+ │ └── FUNDING.yml
56
+ ├── src/
57
+ │ └── ctx/
58
+ │ ├── __init__.py
59
+ │ ├── __main__.py
60
+ │ ├── ...
61
+ │ └── walker.py
62
+ ├── tests/
63
+ │ ├── conftest.py
64
+ │ ├── test_cli.py
65
+ │ ├── test_ignore.py
66
+ │ ├── test_render.py
67
+ │ ├── test_tokens.py
68
+ │ └── test_walker.py
69
+ ├── .gitignore
70
+ ├── CHANGELOG.md
71
+ ├── CONTRIBUTING.md
72
+ ├── LICENSE
73
+ ├── MANIFEST.in
74
+ ├── pyproject.toml
75
+ └── README.md
76
+ ```
77
+ ````
78
+
79
+ ## Features
80
+
81
+ - **One file out.** Directory tree, file contents, and a token estimate in a
82
+ single markdown document — ready to paste into a prompt.
83
+ - **Gitignore-aware.** Follows `.gitignore` (and `.ctxignore`) at every level
84
+ of the tree, with git's semantics: deeper rule files win.
85
+ - **Binary-safe.** Binary files are detected and listed, but their content is
86
+ omitted instead of corrupting the document.
87
+ - **Token-aware.** `--max-tokens` stops early once the running estimate crosses
88
+ the limit, so the output fits your context window.
89
+ - **Filterable.** `--include` / `--exclude` with repeatable pathspec globs, and
90
+ `--max-size` to skip files that are too big.
91
+ - **Offline and light.** The only runtime dependency is `pathspec`.
92
+
93
+ ## Install
94
+
95
+ Requires Python 3.10+. The package is published on PyPI as `repocxt`; the
96
+ command you run is `ctx`.
97
+
98
+ ```bash
99
+ pipx install repocxt
100
+ ```
101
+
102
+ Or install from a checkout directly:
103
+
104
+ ```bash
105
+ pipx install .
106
+ ```
107
+
108
+ Or with plain pip:
109
+
110
+ ```bash
111
+ pip install repocxt
112
+ ```
113
+
114
+ However you install it, you get a `ctx` command (it also runs as
115
+ `python -m ctx`).
116
+
117
+ ## Usage
118
+
119
+ ```text
120
+ usage: ctx [-h] [-o FILE] [--include GLOB] [--exclude GLOB] [--max-size BYTES]
121
+ [--max-tokens N] [--no-tree] [--json] [--version]
122
+ [root]
123
+ ```
124
+
125
+ - `root` — directory to pack. Defaults to the current directory. The header
126
+ uses the directory's name (or `repo` when the name is empty, e.g. `.`).
127
+ - `-o FILE, --output FILE` — write to a file instead of stdout. `-o -` means
128
+ stdout explicitly.
129
+ - `--include GLOB` / `--exclude GLOB` — keep or drop paths matching a glob
130
+ (repeatable; pathspec syntax). Both are applied after ignore filtering, and
131
+ `--exclude` wins over `--include`.
132
+ - `--max-size BYTES` — skip files larger than this many bytes.
133
+ - `--max-tokens N` — stop once the emitted text reaches N tokens.
134
+ - `--no-tree` — omit the ASCII tree section; just the files.
135
+ - `--json` — print a machine-readable summary (file list, counts, bytes,
136
+ tokens) instead of the document.
137
+
138
+ ### With LLMs
139
+
140
+ The output is self-contained, so driving an LLM is two steps:
141
+
142
+ ```bash
143
+ # write it to a file for an agent that can read files
144
+ ctx -o ctx.md
145
+
146
+ # or just paste stdout straight into the prompt
147
+ ctx | head -c 20000
148
+ ```
149
+
150
+ Point the model at `ctx.md` ("this is my whole repo, read it") or paste it
151
+ directly — the tree tells it the layout, the fenced blocks give it the code,
152
+ and the token estimate tells you whether it fits.
153
+
154
+ ## How it decides what to include
155
+
156
+ - **Gitignore semantics.** `.gitignore` files are read at every directory and
157
+ applied to relative paths; a deeper `.gitignore`'s decision wins over a
158
+ shallower one's, and later lines override earlier ones. A private
159
+ `.ctxignore` file is honored the same way, for rules you do not want to
160
+ commit to git.
161
+ - **Always-skipped directories.** `.git`, `.svn`, `.hg`, `.ctx`,
162
+ `__pycache__`, and `.mypy_cache` are always skipped.
163
+ - **Binary omission.** A file is treated as binary when its first 8 KB contain
164
+ a NUL byte — images, archives, compiled objects. It is still listed in the
165
+ document, but with a note instead of content.
166
+ - **Files, not directories.** Only regular files are packed; empty directories
167
+ and directory symlinks do not appear in the tree.
168
+
169
+ ## Philosophy
170
+
171
+ Offline-first. There is no telemetry, no network call, and no tokenizer — token
172
+ counts come from a small local heuristic that errs high, which is the safe
173
+ direction for context budgeting. The dependency list stays minimal
174
+ (`pathspec` is the only runtime dependency) and the output is plain markdown
175
+ you can read, diff, and paste anywhere.
176
+
177
+ ## Development
178
+
179
+ ```bash
180
+ git clone <your fork>/ctx
181
+ cd ctx
182
+ python3 -m venv .venv && source .venv/bin/activate
183
+ pip install -e ".[dev]"
184
+ ```
185
+
186
+ - Run tests: `python -m pytest`
187
+ - Lint: `python -m ruff check .` (config and dev extras live in `pyproject.toml`)
188
+
189
+ No need to install to poke at the CLI:
190
+
191
+ ```bash
192
+ export PYTHONPATH="$PWD/src"
193
+ python3 -m ctx --help
194
+ ```
195
+
196
+ ## Testing & compatibility
197
+
198
+ Honest status of what is and isn't verified:
199
+
200
+ - **Tested on:** Linux, Python 3.12 — 106 automated tests pass (`pytest`),
201
+ `ruff` clean, wheel + sdist build clean, `twine check` passes. Edge cases
202
+ covered by tests: nested/overriding `.gitignore`, negation, directory-only
203
+ patterns, filenames with spaces, undecodable and binary files, oversized
204
+ files, symlinks (not followed for dirs), backtick-fences inside file bodies,
205
+ and `--max-tokens` / `--json` behavior.
206
+ - **Expected but not yet verified:** macOS and Windows, and Python 3.10–3.11.
207
+ The code is pure Python + `pathspec` with no OS-specific calls, so it
208
+ *should* run anywhere, but only Linux/3.12 has been exercised. The GitHub
209
+ Actions matrix (`ci.yml`) runs 3.10–3.12 on Linux for every push; wider
210
+ platform testing is welcome via PRs / CI runs.
211
+ - **Not verified at all:** any real PyPI publication (needs a maintainer token).
212
+
213
+ If you hit a platform you use that isn't green, open an issue with your OS,
214
+ Python version, and the error — that's the fastest way this list gets shorter.
215
+
216
+ ## License
217
+
218
+ [MIT](LICENSE) © 2026 Kamran
@@ -0,0 +1,189 @@
1
+ # ctx
2
+
3
+ Package a repository into a single, LLM-ready context file.
4
+
5
+ `ctx` walks a directory and emits one markdown file containing an ASCII tree of
6
+ the repo, the contents of every source file (fenced with language hints), and a
7
+ token estimate — so you can paste a whole repository into an LLM prompt, or
8
+ point an agent at it, and the model sees structure and code together.
9
+
10
+ ## What it looks like
11
+
12
+ Running `ctx` on this repo (tree trimmed for the README; the header counts are real):
13
+
14
+ ````text
15
+ # repo — context
16
+
17
+ - files: **26** · **67763 bytes** · ~**19876 tokens**
18
+
19
+ ## Tree
20
+
21
+ ```text
22
+ ├── .github/
23
+ │ ├── workflows/
24
+ │ │ ├── ci.yml
25
+ │ │ └── release.yml
26
+ │ └── FUNDING.yml
27
+ ├── src/
28
+ │ └── ctx/
29
+ │ ├── __init__.py
30
+ │ ├── __main__.py
31
+ │ ├── ...
32
+ │ └── walker.py
33
+ ├── tests/
34
+ │ ├── conftest.py
35
+ │ ├── test_cli.py
36
+ │ ├── test_ignore.py
37
+ │ ├── test_render.py
38
+ │ ├── test_tokens.py
39
+ │ └── test_walker.py
40
+ ├── .gitignore
41
+ ├── CHANGELOG.md
42
+ ├── CONTRIBUTING.md
43
+ ├── LICENSE
44
+ ├── MANIFEST.in
45
+ ├── pyproject.toml
46
+ └── README.md
47
+ ```
48
+ ````
49
+
50
+ ## Features
51
+
52
+ - **One file out.** Directory tree, file contents, and a token estimate in a
53
+ single markdown document — ready to paste into a prompt.
54
+ - **Gitignore-aware.** Follows `.gitignore` (and `.ctxignore`) at every level
55
+ of the tree, with git's semantics: deeper rule files win.
56
+ - **Binary-safe.** Binary files are detected and listed, but their content is
57
+ omitted instead of corrupting the document.
58
+ - **Token-aware.** `--max-tokens` stops early once the running estimate crosses
59
+ the limit, so the output fits your context window.
60
+ - **Filterable.** `--include` / `--exclude` with repeatable pathspec globs, and
61
+ `--max-size` to skip files that are too big.
62
+ - **Offline and light.** The only runtime dependency is `pathspec`.
63
+
64
+ ## Install
65
+
66
+ Requires Python 3.10+. The package is published on PyPI as `repocxt`; the
67
+ command you run is `ctx`.
68
+
69
+ ```bash
70
+ pipx install repocxt
71
+ ```
72
+
73
+ Or install from a checkout directly:
74
+
75
+ ```bash
76
+ pipx install .
77
+ ```
78
+
79
+ Or with plain pip:
80
+
81
+ ```bash
82
+ pip install repocxt
83
+ ```
84
+
85
+ However you install it, you get a `ctx` command (it also runs as
86
+ `python -m ctx`).
87
+
88
+ ## Usage
89
+
90
+ ```text
91
+ usage: ctx [-h] [-o FILE] [--include GLOB] [--exclude GLOB] [--max-size BYTES]
92
+ [--max-tokens N] [--no-tree] [--json] [--version]
93
+ [root]
94
+ ```
95
+
96
+ - `root` — directory to pack. Defaults to the current directory. The header
97
+ uses the directory's name (or `repo` when the name is empty, e.g. `.`).
98
+ - `-o FILE, --output FILE` — write to a file instead of stdout. `-o -` means
99
+ stdout explicitly.
100
+ - `--include GLOB` / `--exclude GLOB` — keep or drop paths matching a glob
101
+ (repeatable; pathspec syntax). Both are applied after ignore filtering, and
102
+ `--exclude` wins over `--include`.
103
+ - `--max-size BYTES` — skip files larger than this many bytes.
104
+ - `--max-tokens N` — stop once the emitted text reaches N tokens.
105
+ - `--no-tree` — omit the ASCII tree section; just the files.
106
+ - `--json` — print a machine-readable summary (file list, counts, bytes,
107
+ tokens) instead of the document.
108
+
109
+ ### With LLMs
110
+
111
+ The output is self-contained, so driving an LLM is two steps:
112
+
113
+ ```bash
114
+ # write it to a file for an agent that can read files
115
+ ctx -o ctx.md
116
+
117
+ # or just paste stdout straight into the prompt
118
+ ctx | head -c 20000
119
+ ```
120
+
121
+ Point the model at `ctx.md` ("this is my whole repo, read it") or paste it
122
+ directly — the tree tells it the layout, the fenced blocks give it the code,
123
+ and the token estimate tells you whether it fits.
124
+
125
+ ## How it decides what to include
126
+
127
+ - **Gitignore semantics.** `.gitignore` files are read at every directory and
128
+ applied to relative paths; a deeper `.gitignore`'s decision wins over a
129
+ shallower one's, and later lines override earlier ones. A private
130
+ `.ctxignore` file is honored the same way, for rules you do not want to
131
+ commit to git.
132
+ - **Always-skipped directories.** `.git`, `.svn`, `.hg`, `.ctx`,
133
+ `__pycache__`, and `.mypy_cache` are always skipped.
134
+ - **Binary omission.** A file is treated as binary when its first 8 KB contain
135
+ a NUL byte — images, archives, compiled objects. It is still listed in the
136
+ document, but with a note instead of content.
137
+ - **Files, not directories.** Only regular files are packed; empty directories
138
+ and directory symlinks do not appear in the tree.
139
+
140
+ ## Philosophy
141
+
142
+ Offline-first. There is no telemetry, no network call, and no tokenizer — token
143
+ counts come from a small local heuristic that errs high, which is the safe
144
+ direction for context budgeting. The dependency list stays minimal
145
+ (`pathspec` is the only runtime dependency) and the output is plain markdown
146
+ you can read, diff, and paste anywhere.
147
+
148
+ ## Development
149
+
150
+ ```bash
151
+ git clone <your fork>/ctx
152
+ cd ctx
153
+ python3 -m venv .venv && source .venv/bin/activate
154
+ pip install -e ".[dev]"
155
+ ```
156
+
157
+ - Run tests: `python -m pytest`
158
+ - Lint: `python -m ruff check .` (config and dev extras live in `pyproject.toml`)
159
+
160
+ No need to install to poke at the CLI:
161
+
162
+ ```bash
163
+ export PYTHONPATH="$PWD/src"
164
+ python3 -m ctx --help
165
+ ```
166
+
167
+ ## Testing & compatibility
168
+
169
+ Honest status of what is and isn't verified:
170
+
171
+ - **Tested on:** Linux, Python 3.12 — 106 automated tests pass (`pytest`),
172
+ `ruff` clean, wheel + sdist build clean, `twine check` passes. Edge cases
173
+ covered by tests: nested/overriding `.gitignore`, negation, directory-only
174
+ patterns, filenames with spaces, undecodable and binary files, oversized
175
+ files, symlinks (not followed for dirs), backtick-fences inside file bodies,
176
+ and `--max-tokens` / `--json` behavior.
177
+ - **Expected but not yet verified:** macOS and Windows, and Python 3.10–3.11.
178
+ The code is pure Python + `pathspec` with no OS-specific calls, so it
179
+ *should* run anywhere, but only Linux/3.12 has been exercised. The GitHub
180
+ Actions matrix (`ci.yml`) runs 3.10–3.12 on Linux for every push; wider
181
+ platform testing is welcome via PRs / CI runs.
182
+ - **Not verified at all:** any real PyPI publication (needs a maintainer token).
183
+
184
+ If you hit a platform you use that isn't green, open an issue with your OS,
185
+ Python version, and the error — that's the fastest way this list gets shorter.
186
+
187
+ ## License
188
+
189
+ [MIT](LICENSE) © 2026 Kamran
@@ -0,0 +1,59 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "repocxt"
7
+ version = "0.1.0"
8
+ description = "Package a repository into a single, LLM-ready context file with a tree, file contents, and a token estimate."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [{ name = "Kamran" }]
14
+ keywords = ["llm", "context", "cli", "code", "ai", "prompt"]
15
+ classifiers = [
16
+ "Environment :: Console",
17
+ "Intended Audience :: Developers",
18
+ "Operating System :: OS Independent",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Topic :: Software Development :: Libraries",
24
+ "Topic :: Software Development :: Build Tools",
25
+ ]
26
+ dependencies = [
27
+ "pathspec>=1.1",
28
+ ]
29
+
30
+ [project.optional-dependencies]
31
+ dev = [
32
+ "pytest>=7.4",
33
+ "pytest-cov>=4.1",
34
+ "ruff>=0.4",
35
+ ]
36
+ build = [
37
+ "build>=1.0",
38
+ "twine>=4.0",
39
+ ]
40
+
41
+ [project.scripts]
42
+ ctx = "ctx.cli:main"
43
+
44
+ [tool.setuptools.packages.find]
45
+ where = ["src"]
46
+
47
+ [tool.setuptools.package-data]
48
+ ctx = ["py.typed"]
49
+
50
+ [tool.ruff]
51
+ line-length = 100
52
+ target-version = "py39"
53
+
54
+ [tool.ruff.lint]
55
+ select = ["E", "F", "W", "I", "UP", "B", "SIM", "C4"]
56
+
57
+ [tool.pytest.ini_options]
58
+ testpaths = ["tests"]
59
+ addopts = "-q"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,8 @@
1
+ """ctx — package a repository into a single, LLM-ready context file.
2
+
3
+ The public surface is the ``ctx`` console command, implemented in
4
+ :mod:`ctx.cli`. Importing the package only exposes metadata.
5
+ """
6
+
7
+ __version__ = "0.1.0"
8
+ __all__ = ["__version__"]
@@ -0,0 +1,6 @@
1
+ """Allow ``python -m ctx``."""
2
+
3
+ from ctx.cli import main
4
+
5
+ if __name__ == "__main__":
6
+ raise SystemExit(main())
@@ -0,0 +1,37 @@
1
+ """Heuristic detection of binary files.
2
+
3
+ No file signature database: a file is treated as binary when its first chunk
4
+ contains a NUL byte, which is reliable for the formats programmers actually
5
+ commit (images, archives, compiled objects, fonts, audio). UTF-16 text and some
6
+ encodings can contain NUL, but they are vanishingly rare in repository trees.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from pathlib import Path
12
+
13
+ _SNIFF_BYTES = 8192
14
+ _NUL = b"\x00"
15
+
16
+
17
+ def is_nul_in_text(text: str) -> bool:
18
+ """True when decoded ``text`` still contains a NUL character.
19
+
20
+ ``is_binary`` sniffs only the first chunk, so a NUL past that window would
21
+ otherwise slip through; checking the decoded text is the definitive gate.
22
+ """
23
+ return _NUL.decode("ascii") in text
24
+
25
+
26
+ def is_binary(path: Path) -> bool:
27
+ """Return True when ``path`` looks like a binary file.
28
+
29
+ Reads at most :data:`_SNIFF_BYTES` bytes. Files shorter than that are judged
30
+ on their whole content. A missing file answers False (the caller handles it).
31
+ """
32
+ try:
33
+ with path.open("rb") as handle:
34
+ chunk = handle.read(_SNIFF_BYTES)
35
+ except OSError:
36
+ return False
37
+ return _NUL in chunk