plain 0.50.0__py3-none-any.whl → 0.51.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.
plain/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # plain changelog
2
2
 
3
+ ## [0.51.0](https://github.com/dropseed/plain/releases/plain@0.51.0) (2025-06-24)
4
+
5
+ ### What's changed
6
+
7
+ - New `plain changelog` CLI sub-command to quickly view a package’s changelog from the terminal. Supports `--from`/`--to` flags to limit the version range ([50f0de7](https://github.com/dropseed/plain/commit/50f0de721f263ec6274852bd8838f4e5037b27dc)).
8
+
9
+ ### Upgrade instructions
10
+
11
+ - No changes required
12
+
3
13
  ## [0.50.0](https://github.com/dropseed/plain/releases/plain@0.50.0) (2025-06-23)
4
14
 
5
15
  ### What's changed
@@ -11,23 +21,25 @@
11
21
  ### Upgrade instructions
12
22
 
13
23
  - Update any scripts or documentation that call `plain urls …`:
14
- - Replace `plain urls --flat` with `plain urls list --flat`
24
+ - Replace `plain urls --flat` with `plain urls list --flat`
15
25
  - If you invoke preflight checks in CI or locally:
16
- - Replace `plain preflight --database <alias>` (or multiple aliases) with the new boolean flag: `plain preflight --database`
26
+ - Replace `plain preflight --database <alias>` (or multiple aliases) with the new boolean flag: `plain preflight --database`
17
27
  - In `settings.py` migrate to the new database configuration:
18
- ```python
19
- # Before
20
- DATABASES = {
21
- "default": {
22
- "ENGINE": "plain.backends.sqlite3",
23
- "NAME": BASE_DIR / "db.sqlite3",
24
- }
25
- }
26
-
27
- # After
28
- DATABASE = {
29
- "ENGINE": "plain.backends.sqlite3",
30
- "NAME": BASE_DIR / "db.sqlite3",
31
- }
32
- ```
33
- Remove any `DATABASES` and `DATABASE_ROUTERS` settings – they are no longer read.
28
+
29
+ ```python
30
+ # Before
31
+ DATABASES = {
32
+ "default": {
33
+ "ENGINE": "plain.backends.sqlite3",
34
+ "NAME": BASE_DIR / "db.sqlite3",
35
+ }
36
+ }
37
+
38
+ # After
39
+ DATABASE = {
40
+ "ENGINE": "plain.backends.sqlite3",
41
+ "NAME": BASE_DIR / "db.sqlite3",
42
+ }
43
+ ```
44
+
45
+ Remove any `DATABASES` and `DATABASE_ROUTERS` settings – they are no longer read.
plain/cli/changelog.py ADDED
@@ -0,0 +1,76 @@
1
+ import re
2
+ from importlib.util import find_spec
3
+ from pathlib import Path
4
+
5
+ import click
6
+ from packaging.version import Version
7
+
8
+ from .output import style_markdown
9
+
10
+
11
+ @click.command("changelog")
12
+ @click.argument("package_label")
13
+ @click.option("--from", "from_version", help="Show entries from this version onwards")
14
+ @click.option("--to", "to_version", help="Show entries up to this version")
15
+ def changelog(package_label, from_version, to_version):
16
+ """Show changelog entries for a package."""
17
+ module_name = package_label.replace("-", ".")
18
+ spec = find_spec(module_name)
19
+ if not spec:
20
+ raise click.ClickException(f"Package {package_label} not found")
21
+
22
+ if spec.origin:
23
+ package_path = Path(spec.origin).resolve().parent
24
+ elif spec.submodule_search_locations:
25
+ package_path = Path(list(spec.submodule_search_locations)[0]).resolve()
26
+ else:
27
+ raise click.ClickException(f"Package {package_label} not found")
28
+
29
+ changelog_path = package_path / "CHANGELOG.md"
30
+ if not changelog_path.exists():
31
+ raise click.ClickException(f"Changelog not found for {package_label}")
32
+
33
+ content = changelog_path.read_text()
34
+
35
+ entries = []
36
+ current_version = None
37
+ current_lines = []
38
+ version_re = re.compile(r"^## \[([^\]]+)\]")
39
+
40
+ for line in content.splitlines(keepends=True):
41
+ m = version_re.match(line)
42
+ if m:
43
+ if current_version is not None:
44
+ entries.append((current_version, current_lines))
45
+ current_version = m.group(1)
46
+ current_lines = [line]
47
+ else:
48
+ if current_version is not None:
49
+ current_lines.append(line)
50
+
51
+ if current_version is not None:
52
+ entries.append((current_version, current_lines))
53
+
54
+ def version_found(version):
55
+ return any(Version(v) == Version(version) for v, _ in entries)
56
+
57
+ if from_version and not version_found(from_version):
58
+ click.secho(
59
+ f"Warning: version {from_version} not found in changelog",
60
+ fg="yellow",
61
+ err=True,
62
+ )
63
+
64
+ selected_lines = []
65
+ for version, lines in entries:
66
+ v = Version(version)
67
+ if from_version and v <= Version(from_version):
68
+ continue
69
+ if to_version and v > Version(to_version):
70
+ continue
71
+ selected_lines.extend(lines)
72
+
73
+ if not selected_lines:
74
+ return
75
+
76
+ click.echo(style_markdown("".join(selected_lines)))
plain/cli/core.py CHANGED
@@ -7,6 +7,7 @@ import plain.runtime
7
7
  from plain.exceptions import ImproperlyConfigured
8
8
 
9
9
  from .build import build
10
+ from .changelog import changelog
10
11
  from .chores import chores
11
12
  from .docs import docs
12
13
  from .formatting import PlainContext
@@ -32,6 +33,7 @@ plain_cli.add_command(chores)
32
33
  plain_cli.add_command(build)
33
34
  plain_cli.add_command(utils)
34
35
  plain_cli.add_command(urls)
36
+ plain_cli.add_command(changelog)
35
37
  plain_cli.add_command(setting)
36
38
  plain_cli.add_command(shell)
37
39
  plain_cli.add_command(run)
plain/cli/docs.py CHANGED
@@ -7,6 +7,8 @@ import click
7
7
 
8
8
  from plain.packages import packages_registry
9
9
 
10
+ from .output import iterate_markdown
11
+
10
12
 
11
13
  @click.command()
12
14
  @click.option("--llm", "llm", is_flag=True)
@@ -59,43 +61,7 @@ def docs(module, llm, open):
59
61
  if open:
60
62
  click.launch(str(readme_path))
61
63
  else:
62
-
63
- def _iterate_markdown(content):
64
- """
65
- Iterator that does basic markdown for a Click pager.
66
-
67
- Headings are yellow and bright, code blocks are indented.
68
- """
69
-
70
- in_code_block = False
71
- for line in content.splitlines():
72
- if line.startswith("```"):
73
- in_code_block = not in_code_block
74
-
75
- if in_code_block:
76
- yield click.style(line, dim=True)
77
- elif line.startswith("# "):
78
- yield click.style(line, fg="yellow", bold=True)
79
- elif line.startswith("## "):
80
- yield click.style(line, fg="yellow", bold=True)
81
- elif line.startswith("### "):
82
- yield click.style(line, fg="yellow", bold=True)
83
- elif line.startswith("#### "):
84
- yield click.style(line, fg="yellow", bold=True)
85
- elif line.startswith("##### "):
86
- yield click.style(line, fg="yellow", bold=True)
87
- elif line.startswith("###### "):
88
- yield click.style(line, fg="yellow", bold=True)
89
- elif line.startswith("**") and line.endswith("**"):
90
- yield click.style(line, bold=True)
91
- elif line.startswith("> "):
92
- yield click.style(line, italic=True)
93
- else:
94
- yield line
95
-
96
- yield "\n"
97
-
98
- click.echo_via_pager(_iterate_markdown(readme_path.read_text()))
64
+ click.echo_via_pager(iterate_markdown(readme_path.read_text()))
99
65
 
100
66
 
101
67
  class LLMDocs:
plain/cli/output.py ADDED
@@ -0,0 +1,41 @@
1
+ import click
2
+
3
+
4
+ def style_markdown(content):
5
+ return "".join(iterate_markdown(content))
6
+
7
+
8
+ def iterate_markdown(content):
9
+ """
10
+ Iterator that does basic markdown for a Click pager.
11
+
12
+ Headings are yellow and bright, code blocks are indented.
13
+ """
14
+
15
+ in_code_block = False
16
+ for line in content.splitlines():
17
+ if line.startswith("```"):
18
+ in_code_block = not in_code_block
19
+
20
+ if in_code_block:
21
+ yield click.style(line, dim=True)
22
+ elif line.startswith("# "):
23
+ yield click.style(line, fg="yellow", bold=True)
24
+ elif line.startswith("## "):
25
+ yield click.style(line, fg="yellow", bold=True)
26
+ elif line.startswith("### "):
27
+ yield click.style(line, fg="yellow", bold=True)
28
+ elif line.startswith("#### "):
29
+ yield click.style(line, fg="yellow", bold=True)
30
+ elif line.startswith("##### "):
31
+ yield click.style(line, fg="yellow", bold=True)
32
+ elif line.startswith("###### "):
33
+ yield click.style(line, fg="yellow", bold=True)
34
+ elif line.startswith("**") and line.endswith("**"):
35
+ yield click.style(line, bold=True)
36
+ elif line.startswith("> "):
37
+ yield click.style(line, italic=True)
38
+ else:
39
+ yield line
40
+
41
+ yield "\n"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plain
3
- Version: 0.50.0
3
+ Version: 0.51.0
4
4
  Summary: A web framework for building products with Python.
5
5
  Author-email: Dave Gaeddert <dave.gaeddert@dropseed.dev>
6
6
  License-File: LICENSE
@@ -1,4 +1,4 @@
1
- plain/CHANGELOG.md,sha256=Bl0GLzelbqK3Tyg-dAGBcHG49JwXtH3g6GHFCl-LEpk,1554
1
+ plain/CHANGELOG.md,sha256=lE0zfshrzDi6rsj59KWtZdcM9cqkCtqf7XrC4Tb7JUA,1996
2
2
  plain/README.md,sha256=gik6DBZcJAITcm4WRq_L53AxkjY45eQLafyTCSf0CKE,3986
3
3
  plain/__main__.py,sha256=GK39854Lc_LO_JP8DzY9Y2MIQ4cQEl7SXFJy244-lC8,110
4
4
  plain/debug.py,sha256=abPkJY4aSbBYGEYSZST_ZY3ohXPGDdz9uWQBYRqfd3M,730
@@ -21,11 +21,13 @@ plain/chores/registry.py,sha256=V3WjuekRI22LFvJbqSkUXQtiOtuE2ZK8gKV1TRvxRUI,1866
21
21
  plain/cli/README.md,sha256=GzBry6mEilhM80SfVUg02ydGwAk0m-s6FAqQR1nRsMM,2022
22
22
  plain/cli/__init__.py,sha256=6w9T7K2WrPwh6DcaMb2oNt_CWU6Bc57nUTO2Bt1p38Y,63
23
23
  plain/cli/build.py,sha256=dKUYBNegvb6QNckR7XZ7CJJtINwZcmDvbUdv2dWwjf8,3226
24
+ plain/cli/changelog.py,sha256=VRcL0v_r8tPSe-6fGGsa-OCgdhLMrwj6TnpmPilQb6Y,2441
24
25
  plain/cli/chores.py,sha256=xXSSFvr8T5jWfLWqe6E8YVMw1BkQxyOHHVuY0x9RH0A,2412
25
- plain/cli/core.py,sha256=h8gjeBYQzYTzpmeDMAJTdVDOEoNCNcvnxml-KIsiRPo,2925
26
- plain/cli/docs.py,sha256=2CpTv5k-TNWf593tPiglvUVXWBGdfPmbGf8vl5AfJwU,8995
26
+ plain/cli/core.py,sha256=D3t83ujjjHayblM-RuttrGoNf8hMV9-l3zQsbhVAjWU,2991
27
+ plain/cli/docs.py,sha256=KCJCP_OVFb34zOkA6x7X6-iGFzx2tv4ZgXAM99TjWNg,7443
27
28
  plain/cli/formatting.py,sha256=1hZH13y1qwHcU2K2_Na388nw9uvoeQH8LrWL-O9h8Yc,2207
28
29
  plain/cli/help.py,sha256=otRSGxOJ5V8JMjpdZ8XYqUbdlYdJvxOMzQroLOWw-l0,801
30
+ plain/cli/output.py,sha256=Fe3xS6Va4Bi1ZNrqi0nh09THTsdCyMW2b9SPY5I4n-o,1318
29
31
  plain/cli/preflight.py,sha256=FWFwMZ0W_t8ObTTRMnBmaiGN8PqdEAWgmSEPGDwZFpA,4148
30
32
  plain/cli/print.py,sha256=XraUYrgODOJquIiEv78wSCYGRBplHXtXSS9QtFG5hqY,217
31
33
  plain/cli/registry.py,sha256=yKVMSDjW8g10nlV9sPXFGJQmhC_U-k4J4kM7N2OQVLA,1467
@@ -147,8 +149,8 @@ plain/views/forms.py,sha256=ESZOXuo6IeYixp1RZvPb94KplkowRiwO2eGJCM6zJI0,2400
147
149
  plain/views/objects.py,sha256=GGbcfg_9fPZ-PiaBwIHG2e__8GfWDR7JQtQ15wTyiHg,5970
148
150
  plain/views/redirect.py,sha256=daq2cQIkdDF78bt43sjuZxRAyJm_t_SKw6tyPmiXPIc,1985
149
151
  plain/views/templates.py,sha256=ivkI7LU7BXDQ0d4Geab96Is4-Cp03KbIntXRT1J8e6I,2139
150
- plain-0.50.0.dist-info/METADATA,sha256=GLcRDpS06XPtx52qLG5bIfrROE3g8CFffLNSz6Mdxt8,4297
151
- plain-0.50.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
152
- plain-0.50.0.dist-info/entry_points.txt,sha256=1Ys2lsSeMepD1vz8RSrJopna0RQfUd951vYvCRsvl6A,45
153
- plain-0.50.0.dist-info/licenses/LICENSE,sha256=m0D5O7QoH9l5Vz_rrX_9r-C8d9UNr_ciK6Qwac7o6yo,3175
154
- plain-0.50.0.dist-info/RECORD,,
152
+ plain-0.51.0.dist-info/METADATA,sha256=xzYeAOLD7gvPsfMUMhQJIR0nsBc6VAUZ08bZ1Hyodao,4297
153
+ plain-0.51.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
154
+ plain-0.51.0.dist-info/entry_points.txt,sha256=1Ys2lsSeMepD1vz8RSrJopna0RQfUd951vYvCRsvl6A,45
155
+ plain-0.51.0.dist-info/licenses/LICENSE,sha256=m0D5O7QoH9l5Vz_rrX_9r-C8d9UNr_ciK6Qwac7o6yo,3175
156
+ plain-0.51.0.dist-info/RECORD,,
File without changes