course-setup 3.0.5__tar.gz → 3.0.7__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: course-setup
3
- Version: 3.0.5
3
+ Version: 3.0.7
4
4
  Summary: CLI tools for setting up and retiring GitHub-backed course repositories
5
5
  Author: Reuven Lerner
6
6
  Author-email: Reuven Lerner <reuven@lerner.co.il>
@@ -177,6 +177,10 @@ retire-course ./Acme-2026-03 ./Beta-2026-03 ./Gamma-2026-02
177
177
 
178
178
  If any directory fails, the rest are still processed and errors are reported at the end.
179
179
 
180
+ > **Tip:** run `retire-course` from the *parent* of the course directory, not
181
+ > from inside it. If you're already inside, the command detects it and tells
182
+ > you to `cd ..` rather than failing with a confusing error.
183
+
180
184
  ### `archive-course` — Create a zip archive of a course
181
185
 
182
186
  ```bash
@@ -188,12 +192,13 @@ archive-course ./Acme-python-intro-2026-03
188
192
  | `--output`, `-o` | Custom output zip path (defaults to `{dirname}.zip`) |
189
193
  | `--no-html` | Skip HTML export of Jupyter notebooks |
190
194
 
191
- For Jupyter notebooks, `archive-course` exports each `.ipynb` to HTML via
192
- `nbconvert` and includes both the `.ipynb` and `.html` files in the zip. Use
193
- `--no-html` to skip the HTML export. The archive excludes `.git`, `.venv`,
194
- `__pycache__`, and `.ipynb_checkpoints` directories. After creating the
195
- archive, a summary is printed showing the archive path, file count, size, and
196
- which notebooks were included.
195
+ This archives **all files** in the course directory (`.py`, `.csv`, `.toml`,
196
+ notebooks, etc.), excluding `.git`, `.venv`, `__pycache__`, and
197
+ `.ipynb_checkpoints` directories. For Jupyter notebooks, each `.ipynb` is also
198
+ exported to HTML via `nbconvert` and the `.html` is included alongside the
199
+ original; use `--no-html` to skip the HTML export. After creating the archive,
200
+ a summary is printed listing the archive path, file count, size, notebooks,
201
+ and all other included files.
197
202
 
198
203
  ### `unretire-course` — Restore a retired course
199
204
 
@@ -157,6 +157,10 @@ retire-course ./Acme-2026-03 ./Beta-2026-03 ./Gamma-2026-02
157
157
 
158
158
  If any directory fails, the rest are still processed and errors are reported at the end.
159
159
 
160
+ > **Tip:** run `retire-course` from the *parent* of the course directory, not
161
+ > from inside it. If you're already inside, the command detects it and tells
162
+ > you to `cd ..` rather than failing with a confusing error.
163
+
160
164
  ### `archive-course` — Create a zip archive of a course
161
165
 
162
166
  ```bash
@@ -168,12 +172,13 @@ archive-course ./Acme-python-intro-2026-03
168
172
  | `--output`, `-o` | Custom output zip path (defaults to `{dirname}.zip`) |
169
173
  | `--no-html` | Skip HTML export of Jupyter notebooks |
170
174
 
171
- For Jupyter notebooks, `archive-course` exports each `.ipynb` to HTML via
172
- `nbconvert` and includes both the `.ipynb` and `.html` files in the zip. Use
173
- `--no-html` to skip the HTML export. The archive excludes `.git`, `.venv`,
174
- `__pycache__`, and `.ipynb_checkpoints` directories. After creating the
175
- archive, a summary is printed showing the archive path, file count, size, and
176
- which notebooks were included.
175
+ This archives **all files** in the course directory (`.py`, `.csv`, `.toml`,
176
+ notebooks, etc.), excluding `.git`, `.venv`, `__pycache__`, and
177
+ `.ipynb_checkpoints` directories. For Jupyter notebooks, each `.ipynb` is also
178
+ exported to HTML via `nbconvert` and the `.html` is included alongside the
179
+ original; use `--no-html` to skip the HTML export. After creating the archive,
180
+ a summary is printed listing the archive path, file count, size, notebooks,
181
+ and all other included files.
177
182
 
178
183
  ### `unretire-course` — Restore a retired course
179
184
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "course-setup"
3
- version = "3.0.5"
3
+ version = "3.0.7"
4
4
  description = "CLI tools for setting up and retiring GitHub-backed course repositories"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
@@ -88,6 +88,17 @@ def archive_course(
88
88
  else f"{zip_size / (1024 * 1024):.1f} MB"
89
89
  )
90
90
 
91
+ # Collect non-notebook, non-HTML files for the summary
92
+ notebook_names = {nb.name for nb in notebooks}
93
+ html_names = {nb.with_suffix(".html").name for nb in notebooks}
94
+ with zipfile.ZipFile(zip_path, "r") as zf:
95
+ other_files = sorted(
96
+ Path(name).name
97
+ for name in zf.namelist()
98
+ if Path(name).name not in notebook_names
99
+ and Path(name).name not in html_names
100
+ )
101
+
91
102
  # Print summary
92
103
  print(f"Archive created: {zip_path}")
93
104
  print(f"Files: {file_count}")
@@ -104,6 +115,11 @@ def archive_course(
104
115
  if export_html and html_exported > 0:
105
116
  print(f"HTML exports: {html_exported}")
106
117
 
118
+ if other_files:
119
+ print("Other files:")
120
+ for name in other_files:
121
+ print(f" {name}")
122
+
107
123
  return zip_path
108
124
 
109
125
 
@@ -14,6 +14,36 @@ from setup_course_github import __author__, __email__, __version__, get_github
14
14
  from setup_course_github.config import load_config
15
15
 
16
16
 
17
+ class InsideCourseDirectoryError(RuntimeError):
18
+ """Raised when a command is run from inside the course directory it targets."""
19
+
20
+
21
+ def _check_not_inside_course(dirname: str) -> None:
22
+ """Raise InsideCourseDirectoryError if the user is inside *dirname*.
23
+
24
+ Two cases are detected:
25
+ 1. *dirname* resolves to the current working directory (e.g. ``.`` or
26
+ the absolute path of cwd).
27
+ 2. *dirname* does not exist relative to cwd, but its basename matches
28
+ cwd's basename — the user has cd'd into the directory and then
29
+ passed its bare name on the command line.
30
+ """
31
+ cwd = Path.cwd().resolve()
32
+ target = Path(dirname)
33
+
34
+ inside = False
35
+ if target.exists() and target.resolve() == cwd:
36
+ inside = True
37
+ elif not target.exists() and target.name == cwd.name:
38
+ inside = True
39
+
40
+ if inside:
41
+ raise InsideCourseDirectoryError(
42
+ f"You appear to be inside '{dirname}'. "
43
+ "Move up one directory (cd ..) and run the command again."
44
+ )
45
+
46
+
17
47
  def get_remote_url(dirname: str) -> str:
18
48
  """Return the git remote.origin.url for the repo in dirname."""
19
49
  result = subprocess.run(
@@ -126,6 +156,8 @@ def _build_retirement_summary(
126
156
 
127
157
  def retire_course(dirname: str, keep_public: bool = False) -> None:
128
158
  """Move the local directory to the archive, optionally making the repo private."""
159
+ _check_not_inside_course(dirname)
160
+
129
161
  config = load_config()
130
162
 
131
163
  remote_url = get_remote_url(dirname)
@@ -6,11 +6,17 @@ import sys
6
6
  from pathlib import Path
7
7
 
8
8
  from setup_course_github import __author__, __email__, __version__, get_github
9
- from setup_course_github.retire_course import get_remote_url, parse_repo_name
9
+ from setup_course_github.retire_course import (
10
+ _check_not_inside_course,
11
+ get_remote_url,
12
+ parse_repo_name,
13
+ )
10
14
 
11
15
 
12
16
  def unretire_course(dirname: str) -> None:
13
17
  """Make the repo public again and move the directory to cwd."""
18
+ _check_not_inside_course(dirname)
19
+
14
20
  remote_url = get_remote_url(dirname)
15
21
  repo_name = parse_repo_name(remote_url)
16
22