prompt-comments 0.1.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.
- prompt_comments/__init__.py +21 -0
- prompt_comments/cli.py +62 -0
- prompt_comments/core.py +85 -0
- prompt_comments/dialects.py +189 -0
- prompt_comments/loader.py +34 -0
- prompt_comments/py.typed +0 -0
- prompt_comments-0.1.0.dist-info/METADATA +182 -0
- prompt_comments-0.1.0.dist-info/RECORD +11 -0
- prompt_comments-0.1.0.dist-info/WHEEL +4 -0
- prompt_comments-0.1.0.dist-info/entry_points.txt +2 -0
- prompt_comments-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""prompt_comments: write comments in your AI prompts, strip them before
|
|
2
|
+
they reach the model.
|
|
3
|
+
|
|
4
|
+
>>> from prompt_comments import strip_comments
|
|
5
|
+
>>> strip_comments("Summarize this. {# only for the 'concise' variant #}")
|
|
6
|
+
'Summarize this.'
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from .core import strip_comments
|
|
10
|
+
from .dialects import Dialect, list_dialects
|
|
11
|
+
from .loader import detect_dialect, load_prompt
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"strip_comments",
|
|
15
|
+
"load_prompt",
|
|
16
|
+
"detect_dialect",
|
|
17
|
+
"list_dialects",
|
|
18
|
+
"Dialect",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
__version__ = "0.1.0"
|
prompt_comments/cli.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""``prompt-comments`` command-line interface."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import sys
|
|
7
|
+
from typing import Optional, Sequence
|
|
8
|
+
|
|
9
|
+
from .core import strip_comments
|
|
10
|
+
from .dialects import list_dialects
|
|
11
|
+
from .loader import detect_dialect
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
15
|
+
parser = argparse.ArgumentParser(
|
|
16
|
+
prog="prompt-comments",
|
|
17
|
+
description="Strip comments from a prompt file (or stdin) and print the result.",
|
|
18
|
+
)
|
|
19
|
+
parser.add_argument("path", nargs="?", help="Prompt file to clean. Reads stdin if omitted.")
|
|
20
|
+
parser.add_argument(
|
|
21
|
+
"-d",
|
|
22
|
+
"--dialect",
|
|
23
|
+
action="append",
|
|
24
|
+
help="Comment dialect to strip (repeatable). Default: inferred from the file "
|
|
25
|
+
"extension, or 'default' when reading stdin.",
|
|
26
|
+
)
|
|
27
|
+
parser.add_argument(
|
|
28
|
+
"--strip-blank-lines",
|
|
29
|
+
action="store_true",
|
|
30
|
+
help="Drop lines that become empty after their comment is removed.",
|
|
31
|
+
)
|
|
32
|
+
parser.add_argument(
|
|
33
|
+
"--list-dialects",
|
|
34
|
+
action="store_true",
|
|
35
|
+
help="List available dialect names and exit.",
|
|
36
|
+
)
|
|
37
|
+
return parser
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def main(argv: Optional[Sequence[str]] = None) -> int:
|
|
41
|
+
parser = build_parser()
|
|
42
|
+
args = parser.parse_args(argv)
|
|
43
|
+
|
|
44
|
+
if args.list_dialects:
|
|
45
|
+
for name in list_dialects():
|
|
46
|
+
print(name)
|
|
47
|
+
return 0
|
|
48
|
+
|
|
49
|
+
if args.path:
|
|
50
|
+
with open(args.path, "r", encoding="utf-8") as f:
|
|
51
|
+
text = f.read()
|
|
52
|
+
dialect = args.dialect or detect_dialect(args.path)
|
|
53
|
+
else:
|
|
54
|
+
text = sys.stdin.read()
|
|
55
|
+
dialect = args.dialect or "default"
|
|
56
|
+
|
|
57
|
+
sys.stdout.write(strip_comments(text, dialect=dialect, strip_blank_lines=args.strip_blank_lines))
|
|
58
|
+
return 0
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
if __name__ == "__main__":
|
|
62
|
+
raise SystemExit(main())
|
prompt_comments/core.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Core comment-stripping logic."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from typing import Iterable, Union
|
|
7
|
+
|
|
8
|
+
from .dialects import Dialect, resolve_dialects
|
|
9
|
+
|
|
10
|
+
DialectLike = Union[str, Dialect, Iterable[Union[str, Dialect]]]
|
|
11
|
+
|
|
12
|
+
_ESCAPE_PLACEHOLDER = "\0PROMPT_COMMENTS_ESCAPED\0"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def strip_comments(text: str, dialect: DialectLike = "default", *, strip_blank_lines: bool = False) -> str:
|
|
16
|
+
"""Remove comments from ``text``.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
text: The prompt text to clean.
|
|
20
|
+
dialect: A dialect name (e.g. ``"jinja"``), a :class:`~prompt_comments.dialects.Dialect`
|
|
21
|
+
instance, or a list mixing either. Defaults to ``"default"``, a small
|
|
22
|
+
set of comment styles ( ``{# #}``, ``<!-- -->``, ``//`` ) chosen to be
|
|
23
|
+
unlikely to collide with real prompt text. See
|
|
24
|
+
:func:`prompt_comments.dialects.list_dialects` for all available names.
|
|
25
|
+
strip_blank_lines: If ``True``, lines that become empty after a comment
|
|
26
|
+
is removed are dropped entirely instead of left as blank lines.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
The text with all matching comments removed.
|
|
30
|
+
|
|
31
|
+
A line-comment marker can be kept literally by escaping it with a
|
|
32
|
+
backslash, e.g. ``\\# not a comment``.
|
|
33
|
+
"""
|
|
34
|
+
dialects = resolve_dialects(dialect)
|
|
35
|
+
text = _strip_block_comments(text, dialects)
|
|
36
|
+
text = _strip_line_comments(text, dialects)
|
|
37
|
+
if strip_blank_lines:
|
|
38
|
+
text = "\n".join(line for line in text.split("\n") if line.strip())
|
|
39
|
+
return text
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _strip_block_comments(text: str, dialects) -> str:
|
|
43
|
+
blocks = []
|
|
44
|
+
seen = set()
|
|
45
|
+
for d in dialects:
|
|
46
|
+
for block in d.block_comments:
|
|
47
|
+
key = (block.start, block.end)
|
|
48
|
+
if key not in seen:
|
|
49
|
+
seen.add(key)
|
|
50
|
+
blocks.append(block)
|
|
51
|
+
if not blocks:
|
|
52
|
+
return text
|
|
53
|
+
|
|
54
|
+
# Longer start delimiters first, so e.g. `{{!--` is tried before `{{!`.
|
|
55
|
+
blocks.sort(key=lambda b: len(b.start), reverse=True)
|
|
56
|
+
pattern = "|".join(re.escape(b.start) + r".*?" + re.escape(b.end) for b in blocks)
|
|
57
|
+
return re.sub(pattern, "", text, flags=re.DOTALL)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _strip_line_comments(text: str, dialects) -> str:
|
|
61
|
+
markers = set()
|
|
62
|
+
for d in dialects:
|
|
63
|
+
for lc in d.line_comments:
|
|
64
|
+
markers.add(lc.marker)
|
|
65
|
+
if not markers:
|
|
66
|
+
return text
|
|
67
|
+
|
|
68
|
+
# Longest markers first so e.g. `--` isn't a partial match ahead of a
|
|
69
|
+
# hypothetical `---` marker.
|
|
70
|
+
ordered = sorted(markers, key=len, reverse=True)
|
|
71
|
+
marker_pattern = "|".join(re.escape(m) for m in ordered)
|
|
72
|
+
|
|
73
|
+
escaped_re = re.compile(r"\\(" + marker_pattern + r")")
|
|
74
|
+
text = escaped_re.sub(lambda m: _ESCAPE_PLACEHOLDER + m.group(1), text)
|
|
75
|
+
|
|
76
|
+
# A marker only starts a comment at the beginning of a line or after
|
|
77
|
+
# whitespace -- this keeps things like `https://example.com` and
|
|
78
|
+
# `color: #fff` mid-token intact in common cases (URLs specifically:
|
|
79
|
+
# the char before `//` there is `:`, not whitespace, so it's skipped).
|
|
80
|
+
comment_re = re.compile(r"(?:^|(?<=\s))(?:" + marker_pattern + r").*$", re.MULTILINE)
|
|
81
|
+
text = comment_re.sub("", text)
|
|
82
|
+
|
|
83
|
+
text = text.replace(_ESCAPE_PLACEHOLDER, "")
|
|
84
|
+
text = re.sub(r"[ \t]+$", "", text, flags=re.MULTILINE)
|
|
85
|
+
return text
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"""Comment-syntax definitions for the file formats and template engines
|
|
2
|
+
that AI prompts commonly live in.
|
|
3
|
+
|
|
4
|
+
A :class:`Dialect` is just a named bundle of line-comment markers and
|
|
5
|
+
block-comment delimiters. Dialects can be combined freely -- pass a list
|
|
6
|
+
of names to :func:`prompt_comments.strip_comments` to strip several
|
|
7
|
+
comment styles from the same text at once (handy for e.g. a Jinja
|
|
8
|
+
template that is also a Markdown document).
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
from typing import Dict, Tuple
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True)
|
|
18
|
+
class LineComment:
|
|
19
|
+
"""A marker that comments out everything from itself to end of line."""
|
|
20
|
+
|
|
21
|
+
marker: str
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True)
|
|
25
|
+
class BlockComment:
|
|
26
|
+
"""A start/end delimiter pair that comments out everything between."""
|
|
27
|
+
|
|
28
|
+
start: str
|
|
29
|
+
end: str
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True)
|
|
33
|
+
class Dialect:
|
|
34
|
+
"""A named collection of comment styles."""
|
|
35
|
+
|
|
36
|
+
name: str
|
|
37
|
+
line_comments: Tuple[LineComment, ...] = field(default_factory=tuple)
|
|
38
|
+
block_comments: Tuple[BlockComment, ...] = field(default_factory=tuple)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# --- Reusable comment-style building blocks --------------------------------
|
|
42
|
+
|
|
43
|
+
_HASH_LINE = LineComment("#")
|
|
44
|
+
_SLASH_LINE = LineComment("//")
|
|
45
|
+
_DASH_LINE = LineComment("--")
|
|
46
|
+
_SEMI_LINE = LineComment(";")
|
|
47
|
+
_PERCENT_LINE = LineComment("%")
|
|
48
|
+
|
|
49
|
+
_JINJA_BLOCK = BlockComment("{#", "#}")
|
|
50
|
+
_HTML_BLOCK = BlockComment("<!--", "-->")
|
|
51
|
+
_C_BLOCK = BlockComment("/*", "*/")
|
|
52
|
+
_LIQUID_BLOCK = BlockComment("{% comment %}", "{% endcomment %}")
|
|
53
|
+
_MUSTACHE_LONG_BLOCK = BlockComment("{{!--", "--}}")
|
|
54
|
+
_MUSTACHE_SHORT_BLOCK = BlockComment("{{!", "}}")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _dialect(name: str, *, line=(), block=()) -> Dialect:
|
|
58
|
+
return Dialect(name=name, line_comments=tuple(line), block_comments=tuple(block))
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# --- Named dialects ----------------------------------------------------------
|
|
62
|
+
#
|
|
63
|
+
# "default" is deliberately small: only comment styles that are very
|
|
64
|
+
# unlikely to collide with real prompt text. Format-specific dialects
|
|
65
|
+
# (yaml, python, sql, ...) add markers like `#`, `--`, `%` that *do*
|
|
66
|
+
# collide with plausible prompt content (Markdown headings, "item #3",
|
|
67
|
+
# LaTeX-ish text) -- opt into those explicitly via the `dialect` argument.
|
|
68
|
+
|
|
69
|
+
_BASE: Dict[str, Dialect] = {
|
|
70
|
+
"default": _dialect("default", line=[_SLASH_LINE], block=[_JINJA_BLOCK, _HTML_BLOCK]),
|
|
71
|
+
"jinja": _dialect("jinja", block=[_JINJA_BLOCK]),
|
|
72
|
+
"django": _dialect("django", block=[_JINJA_BLOCK, _LIQUID_BLOCK]),
|
|
73
|
+
"liquid": _dialect("liquid", block=[_LIQUID_BLOCK]),
|
|
74
|
+
"handlebars": _dialect("handlebars", block=[_MUSTACHE_LONG_BLOCK, _MUSTACHE_SHORT_BLOCK]),
|
|
75
|
+
"markdown": _dialect("markdown", block=[_HTML_BLOCK]),
|
|
76
|
+
"html": _dialect("html", block=[_HTML_BLOCK]),
|
|
77
|
+
"xml": _dialect("xml", block=[_HTML_BLOCK]),
|
|
78
|
+
"yaml": _dialect("yaml", line=[_HASH_LINE]),
|
|
79
|
+
"toml": _dialect("toml", line=[_HASH_LINE]),
|
|
80
|
+
"ini": _dialect("ini", line=[_HASH_LINE, _SEMI_LINE]),
|
|
81
|
+
"env": _dialect("env", line=[_HASH_LINE]),
|
|
82
|
+
"json5": _dialect("json5", line=[_SLASH_LINE], block=[_C_BLOCK]),
|
|
83
|
+
"python": _dialect("python", line=[_HASH_LINE]),
|
|
84
|
+
"shell": _dialect("shell", line=[_HASH_LINE]),
|
|
85
|
+
"sql": _dialect("sql", line=[_DASH_LINE], block=[_C_BLOCK]),
|
|
86
|
+
"latex": _dialect("latex", line=[_PERCENT_LINE]),
|
|
87
|
+
"c": _dialect("c", line=[_SLASH_LINE], block=[_C_BLOCK]),
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
# Aliases: alternate names that map to an existing dialect above.
|
|
91
|
+
_ALIASES: Dict[str, str] = {
|
|
92
|
+
"jinja2": "jinja",
|
|
93
|
+
"mustache": "handlebars",
|
|
94
|
+
"jsonc": "json5",
|
|
95
|
+
"bash": "shell",
|
|
96
|
+
"sh": "shell",
|
|
97
|
+
"zsh": "shell",
|
|
98
|
+
"js": "c",
|
|
99
|
+
"javascript": "c",
|
|
100
|
+
"ts": "c",
|
|
101
|
+
"typescript": "c",
|
|
102
|
+
"go": "c",
|
|
103
|
+
"golang": "c",
|
|
104
|
+
"rust": "c",
|
|
105
|
+
"java": "c",
|
|
106
|
+
"csharp": "c",
|
|
107
|
+
"cs": "c",
|
|
108
|
+
"cpp": "c",
|
|
109
|
+
"c++": "c",
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
DIALECTS: Dict[str, Dialect] = dict(_BASE)
|
|
113
|
+
for _alias, _target in _ALIASES.items():
|
|
114
|
+
DIALECTS[_alias] = DIALECTS[_target]
|
|
115
|
+
|
|
116
|
+
# "all": every comment style from every named dialect, deduplicated. Handy
|
|
117
|
+
# as a maximal default, though it maximizes the odds of a false-positive
|
|
118
|
+
# match against literal prompt text -- prefer naming specific dialects.
|
|
119
|
+
_all_line = {lc for d in _BASE.values() for lc in d.line_comments}
|
|
120
|
+
_all_block = {bc for d in _BASE.values() for bc in d.block_comments}
|
|
121
|
+
DIALECTS["all"] = _dialect("all", line=sorted(_all_line, key=lambda l: l.marker), block=sorted(_all_block, key=lambda b: b.start))
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def resolve_dialects(dialect):
|
|
125
|
+
"""Normalize a dialect argument into a list of :class:`Dialect` objects.
|
|
126
|
+
|
|
127
|
+
`dialect` may be a dialect name, a :class:`Dialect` instance, or an
|
|
128
|
+
iterable mixing either.
|
|
129
|
+
"""
|
|
130
|
+
if isinstance(dialect, (str, Dialect)):
|
|
131
|
+
items = [dialect]
|
|
132
|
+
else:
|
|
133
|
+
items = list(dialect)
|
|
134
|
+
|
|
135
|
+
resolved = []
|
|
136
|
+
for item in items:
|
|
137
|
+
if isinstance(item, Dialect):
|
|
138
|
+
resolved.append(item)
|
|
139
|
+
continue
|
|
140
|
+
name = str(item).lower()
|
|
141
|
+
if name not in DIALECTS:
|
|
142
|
+
raise ValueError(
|
|
143
|
+
f"Unknown dialect {item!r}. Available dialects: {', '.join(sorted(DIALECTS))}"
|
|
144
|
+
)
|
|
145
|
+
resolved.append(DIALECTS[name])
|
|
146
|
+
return resolved
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def list_dialects():
|
|
150
|
+
"""Return the sorted list of known dialect names (including aliases)."""
|
|
151
|
+
return sorted(DIALECTS)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
# --- File-extension -> dialect mapping, used by prompt_comments.loader -----
|
|
155
|
+
|
|
156
|
+
EXTENSION_DIALECTS: Dict[str, str] = {
|
|
157
|
+
".md": "markdown",
|
|
158
|
+
".markdown": "markdown",
|
|
159
|
+
".j2": "jinja",
|
|
160
|
+
".jinja": "jinja",
|
|
161
|
+
".jinja2": "jinja",
|
|
162
|
+
".html": "html",
|
|
163
|
+
".htm": "html",
|
|
164
|
+
".xml": "xml",
|
|
165
|
+
".yaml": "yaml",
|
|
166
|
+
".yml": "yaml",
|
|
167
|
+
".toml": "toml",
|
|
168
|
+
".ini": "ini",
|
|
169
|
+
".cfg": "ini",
|
|
170
|
+
".env": "env",
|
|
171
|
+
".json5": "json5",
|
|
172
|
+
".jsonc": "jsonc",
|
|
173
|
+
".py": "python",
|
|
174
|
+
".sh": "shell",
|
|
175
|
+
".bash": "shell",
|
|
176
|
+
".sql": "sql",
|
|
177
|
+
".tex": "latex",
|
|
178
|
+
".liquid": "liquid",
|
|
179
|
+
".hbs": "handlebars",
|
|
180
|
+
".handlebars": "handlebars",
|
|
181
|
+
".mustache": "mustache",
|
|
182
|
+
".js": "javascript",
|
|
183
|
+
".ts": "typescript",
|
|
184
|
+
".go": "go",
|
|
185
|
+
".rs": "rust",
|
|
186
|
+
".java": "java",
|
|
187
|
+
".cs": "csharp",
|
|
188
|
+
".txt": "default",
|
|
189
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""File-aware convenience wrapper around :func:`prompt_comments.core.strip_comments`."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional, Union
|
|
7
|
+
|
|
8
|
+
from .core import DialectLike, strip_comments
|
|
9
|
+
from .dialects import EXTENSION_DIALECTS
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def detect_dialect(path: Union[str, Path]) -> str:
|
|
13
|
+
"""Guess a dialect name from a file's extension, defaulting to ``"default"``."""
|
|
14
|
+
return EXTENSION_DIALECTS.get(Path(path).suffix.lower(), "default")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def load_prompt(
|
|
18
|
+
path: Union[str, Path],
|
|
19
|
+
dialect: Optional[DialectLike] = None,
|
|
20
|
+
*,
|
|
21
|
+
encoding: str = "utf-8",
|
|
22
|
+
strip_blank_lines: bool = False,
|
|
23
|
+
) -> str:
|
|
24
|
+
"""Read a prompt file and return it with comments stripped.
|
|
25
|
+
|
|
26
|
+
If ``dialect`` is omitted, it is inferred from the file extension
|
|
27
|
+
(e.g. ``.j2`` -> ``jinja``, ``.md`` -> ``markdown``), falling back to
|
|
28
|
+
``"default"`` for unrecognized extensions.
|
|
29
|
+
"""
|
|
30
|
+
path = Path(path)
|
|
31
|
+
text = path.read_text(encoding=encoding)
|
|
32
|
+
if dialect is None:
|
|
33
|
+
dialect = detect_dialect(path)
|
|
34
|
+
return strip_comments(text, dialect=dialect, strip_blank_lines=strip_blank_lines)
|
prompt_comments/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: prompt-comments
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Write comments in your AI prompts, strip them before they reach the model.
|
|
5
|
+
Project-URL: Homepage, https://github.com/viswanathms/prompt-comments
|
|
6
|
+
Project-URL: Repository, https://github.com/viswanathms/prompt-comments
|
|
7
|
+
Project-URL: Issues, https://github.com/viswanathms/prompt-comments/issues
|
|
8
|
+
Author: viswanathms
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: ai,comments,jinja,llm,markdown,prompt,prompt-engineering,template
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
25
|
+
Classifier: Topic :: Text Processing :: Filters
|
|
26
|
+
Requires-Python: >=3.8
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# prompt-comments
|
|
32
|
+
|
|
33
|
+
[](https://github.com/viswanathms/prompt-comments/actions/workflows/ci.yml)
|
|
34
|
+
[](https://pypi.org/project/prompt-comments/)
|
|
35
|
+
[](LICENSE)
|
|
36
|
+
|
|
37
|
+
Write comments in your AI prompts. Strip them before they reach the model.
|
|
38
|
+
|
|
39
|
+
Prompts live in Markdown files, Jinja templates, YAML configs, plain
|
|
40
|
+
`.txt` files — but none of those give you a way to leave a note for
|
|
41
|
+
your future self ("why is this instruction here?", "TODO: tighten
|
|
42
|
+
this up") without the note leaking into the actual text sent to the
|
|
43
|
+
model. `prompt-comments` strips comments in whatever format your
|
|
44
|
+
prompt already uses, right before you hand the text to your LLM SDK
|
|
45
|
+
of choice.
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from prompt_comments import strip_comments
|
|
49
|
+
|
|
50
|
+
prompt = """
|
|
51
|
+
You are a helpful support assistant. {# keep tone friendly, not robotic #}
|
|
52
|
+
Always answer in the user's language. <!-- TODO: revisit for RTL languages -->
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
strip_comments(prompt)
|
|
56
|
+
# "\nYou are a helpful support assistant.\nAlways answer in the user's language.\n"
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Install
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install prompt-comments
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Usage
|
|
66
|
+
|
|
67
|
+
### Strip comments from a string
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from prompt_comments import strip_comments
|
|
71
|
+
|
|
72
|
+
strip_comments("Summarize this. {# only for the 'concise' variant #}")
|
|
73
|
+
# "Summarize this."
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
By default, `strip_comments` removes a small set of comment styles
|
|
77
|
+
that are unlikely to collide with real prompt text: Jinja-style block
|
|
78
|
+
comments (`{# ... #}`), HTML/Markdown block comments (`<!-- ... -->`),
|
|
79
|
+
and `//` line comments. Pick a different (or additional) dialect for
|
|
80
|
+
formats with their own conventions:
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
strip_comments(open("system_prompt.yaml").read(), dialect="yaml")
|
|
84
|
+
|
|
85
|
+
# Combine multiple dialects
|
|
86
|
+
strip_comments(text, dialect=["jinja", "yaml"])
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Load a prompt straight from a file
|
|
90
|
+
|
|
91
|
+
`load_prompt` reads a file and infers the right dialect from its
|
|
92
|
+
extension automatically (`.j2`/`.jinja2` → Jinja, `.md` → Markdown,
|
|
93
|
+
`.yaml` → YAML, ...):
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from prompt_comments import load_prompt
|
|
97
|
+
|
|
98
|
+
system_prompt = load_prompt("prompts/system.md")
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Override the inferred dialect if needed:
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
load_prompt("prompts/system.txt", dialect="yaml")
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Command line
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
prompt-comments prompts/system.md # auto-detects dialect from extension
|
|
111
|
+
prompt-comments prompts/system.txt -d yaml # force a dialect
|
|
112
|
+
cat prompt.txt | prompt-comments -d jinja # read from stdin
|
|
113
|
+
prompt-comments --list-dialects
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Escaping a literal marker
|
|
117
|
+
|
|
118
|
+
Prefix a line-comment marker with `\` to keep it literally instead of
|
|
119
|
+
treating it as a comment:
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
strip_comments(r"The meeting room is \#3 today", dialect="yaml")
|
|
123
|
+
# "The meeting room is #3 today"
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Dropping now-empty lines
|
|
127
|
+
|
|
128
|
+
Comments that occupied a whole line leave a blank line behind by
|
|
129
|
+
default. Pass `strip_blank_lines=True` to drop them instead:
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
strip_comments("keep\n// only a comment\nkeep too", dialect="c", strip_blank_lines=True)
|
|
133
|
+
# "keep\nkeep too"
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Supported dialects
|
|
137
|
+
|
|
138
|
+
| Dialect | Line comments | Block comments |
|
|
139
|
+
|---|---|---|
|
|
140
|
+
| `default` | `//` | `{# #}`, `<!-- -->` |
|
|
141
|
+
| `jinja` / `jinja2` | — | `{# #}` |
|
|
142
|
+
| `django` | — | `{# #}`, `{% comment %}...{% endcomment %}` |
|
|
143
|
+
| `liquid` | — | `{% comment %}...{% endcomment %}` |
|
|
144
|
+
| `handlebars` / `mustache` | — | `{{! }}`, `{{!-- --}}` |
|
|
145
|
+
| `markdown` / `html` / `xml` | — | `<!-- -->` |
|
|
146
|
+
| `yaml` / `toml` / `env` | `#` | — |
|
|
147
|
+
| `ini` | `#`, `;` | — |
|
|
148
|
+
| `json5` / `jsonc` | `//` | `/* */` |
|
|
149
|
+
| `python` | `#` | — |
|
|
150
|
+
| `shell` / `bash` / `sh` / `zsh` | `#` | — |
|
|
151
|
+
| `sql` | `--` | `/* */` |
|
|
152
|
+
| `latex` | `%` | — |
|
|
153
|
+
| `c` / `js` / `ts` / `go` / `rust` / `java` / `csharp` / ... | `//` | `/* */` |
|
|
154
|
+
| `all` | every marker above | every block style above |
|
|
155
|
+
|
|
156
|
+
Run `prompt-comments --list-dialects` (or `prompt_comments.list_dialects()`) for the exact, current list.
|
|
157
|
+
|
|
158
|
+
Dialects can be combined by passing a list, e.g. `dialect=["yaml", "jinja"]`.
|
|
159
|
+
|
|
160
|
+
## A note on false positives
|
|
161
|
+
|
|
162
|
+
Comment stripping here is regex-based, not a full parser for every
|
|
163
|
+
format. A line-comment marker only starts a comment when it's at the
|
|
164
|
+
start of a line or preceded by whitespace (this is why
|
|
165
|
+
`https://example.com` survives the `//` marker: the `//` is preceded
|
|
166
|
+
by `:`, not whitespace). It is still a heuristic — e.g. `# Heading`
|
|
167
|
+
in a Markdown file *would* be stripped if you opted into the `yaml`
|
|
168
|
+
or `python` dialect (which treat `#` as a comment marker) on that
|
|
169
|
+
text. Stick to `default`/`markdown`/`jinja`-style dialects for prose
|
|
170
|
+
prompts with literal `#`, `--`, or `%` characters, or escape the
|
|
171
|
+
character with a backslash where it must be kept literally.
|
|
172
|
+
|
|
173
|
+
## Development
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
pip install -e ".[dev]"
|
|
177
|
+
pytest
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## License
|
|
181
|
+
|
|
182
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
prompt_comments/__init__.py,sha256=6-6T_fiCRuUdgRDTeL16RcXoNYLZIc6-JK4cprtH5IQ,516
|
|
2
|
+
prompt_comments/cli.py,sha256=sJSfXyV78x5dI7P8cHTobVyTkdjClSnL5VzVEAuTKBw,1781
|
|
3
|
+
prompt_comments/core.py,sha256=Fsv74xiLZWw2vy9F3GDiSgkTABL1SbbeGgarZsSIKWI,3211
|
|
4
|
+
prompt_comments/dialects.py,sha256=yHNxkaRkprSpB4UFyIvJKi1kEuG4FetWY82JzJmPb-U,6058
|
|
5
|
+
prompt_comments/loader.py,sha256=8xBqAlo8TpFakoQskQeS1yrT7D_JTHd7PoabiOVRG9s,1137
|
|
6
|
+
prompt_comments/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
prompt_comments-0.1.0.dist-info/METADATA,sha256=R4DqJ-ELHOmz-oXV_edhwOI_zDJRQ-ILYmfu2dv2ir8,6217
|
|
8
|
+
prompt_comments-0.1.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
9
|
+
prompt_comments-0.1.0.dist-info/entry_points.txt,sha256=rKJBxDThswf4hTZbNbwfY5YRkHXceQCL77sIh95AUeI,61
|
|
10
|
+
prompt_comments-0.1.0.dist-info/licenses/LICENSE,sha256=SAu_6EceT4QbM51RkHTKR8jIfJ2wXFNRq_-4yE6jMsQ,1068
|
|
11
|
+
prompt_comments-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 viswanathms
|
|
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.
|