reposhowcase 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.
- reposhowcase-0.1.0/LICENSE +21 -0
- reposhowcase-0.1.0/PKG-INFO +126 -0
- reposhowcase-0.1.0/README.md +106 -0
- reposhowcase-0.1.0/pyproject.toml +31 -0
- reposhowcase-0.1.0/setup.cfg +4 -0
- reposhowcase-0.1.0/src/reposhowcase/__init__.py +15 -0
- reposhowcase-0.1.0/src/reposhowcase/analyzer.py +132 -0
- reposhowcase-0.1.0/src/reposhowcase/cli.py +36 -0
- reposhowcase-0.1.0/src/reposhowcase/exporters/__init__.py +9 -0
- reposhowcase-0.1.0/src/reposhowcase/exporters/html.py +40 -0
- reposhowcase-0.1.0/src/reposhowcase/exporters/mp4.py +56 -0
- reposhowcase-0.1.0/src/reposhowcase/exporters/pptx.py +28 -0
- reposhowcase-0.1.0/src/reposhowcase/models.py +59 -0
- reposhowcase-0.1.0/src/reposhowcase/storyboard.py +30 -0
- reposhowcase-0.1.0/src/reposhowcase.egg-info/PKG-INFO +126 -0
- reposhowcase-0.1.0/src/reposhowcase.egg-info/SOURCES.txt +19 -0
- reposhowcase-0.1.0/src/reposhowcase.egg-info/dependency_links.txt +1 -0
- reposhowcase-0.1.0/src/reposhowcase.egg-info/entry_points.txt +2 -0
- reposhowcase-0.1.0/src/reposhowcase.egg-info/requires.txt +10 -0
- reposhowcase-0.1.0/src/reposhowcase.egg-info/top_level.txt +1 -0
- reposhowcase-0.1.0/tests/test_reposhowcase.py +47 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 RepoShowcase Team
|
|
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,126 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: reposhowcase
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Evidence-based showcases for multi-language software repositories.
|
|
5
|
+
Author: RepoShowcase contributors
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/OttoHui/reposhowcase
|
|
8
|
+
Project-URL: Repository, https://github.com/OttoHui/reposhowcase
|
|
9
|
+
Project-URL: Issues, https://github.com/OttoHui/reposhowcase/issues
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: tomli>=2; python_version < "3.11"
|
|
14
|
+
Provides-Extra: pptx
|
|
15
|
+
Requires-Dist: python-pptx>=0.6.23; extra == "pptx"
|
|
16
|
+
Provides-Extra: dev
|
|
17
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
18
|
+
Requires-Dist: ruff>=0.6; extra == "dev"
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
# RepoShowcase
|
|
22
|
+
|
|
23
|
+
RepoShowcase analyzes an existing multi-language software repository and turns
|
|
24
|
+
verified repository facts into an evidence-based project showcase. The same
|
|
25
|
+
storyboard can be exported as an interactive HTML presentation, an editable
|
|
26
|
+
PowerPoint file, or an MP4 video.
|
|
27
|
+
|
|
28
|
+
## Motivation
|
|
29
|
+
|
|
30
|
+
Explaining an unfamiliar software project usually requires manually collecting
|
|
31
|
+
facts from its README, source tree, dependency files, tests, and Git history.
|
|
32
|
+
That process is slow and can lead to claims that are difficult to verify.
|
|
33
|
+
RepoShowcase automates the evidence-gathering stage while keeping each claim
|
|
34
|
+
linked to the repository-relative path that supports it.
|
|
35
|
+
|
|
36
|
+
## What makes it different
|
|
37
|
+
|
|
38
|
+
- **Evidence before presentation:** generated claims come from files and Git
|
|
39
|
+
data found in the repository rather than invented project descriptions.
|
|
40
|
+
- **One model, three outputs:** HTML, PPTX, and MP4 are generated from the same
|
|
41
|
+
storyboard, so the formats remain consistent.
|
|
42
|
+
- **Multi-language fallback:** common languages receive file-level detection,
|
|
43
|
+
while unfamiliar file types still contribute generic repository evidence.
|
|
44
|
+
- **Useful for existing projects:** the target repository does not need to
|
|
45
|
+
follow a special framework or add RepoShowcase-specific annotations.
|
|
46
|
+
|
|
47
|
+
## Example use cases
|
|
48
|
+
|
|
49
|
+
- Give a one-minute overview of a team software project.
|
|
50
|
+
- Create a review presentation for an unfamiliar open-source repository.
|
|
51
|
+
- Produce an evidence-backed project summary for a class demonstration.
|
|
52
|
+
- Generate a machine-readable JSON report for later analysis or tooling.
|
|
53
|
+
|
|
54
|
+
## Quick start
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install -e ".[dev]"
|
|
58
|
+
reposhowcase build ./my-project --outputs html,pptx,mp4 --output showcase/
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
HTML is dependency-free. PPTX requires the `pptx` extra, and MP4 requires an
|
|
62
|
+
`ffmpeg` executable on `PATH`.
|
|
63
|
+
|
|
64
|
+
After the first PyPI release, the installation command will become:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pip install reposhowcase
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
For development with optional exporters and test tools:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pip install -e ".[dev,pptx]"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Analysis pipeline
|
|
77
|
+
|
|
78
|
+
```text
|
|
79
|
+
scan repository
|
|
80
|
+
-> detect languages and project files
|
|
81
|
+
-> extract README, dependency, test, symbol, and Git evidence
|
|
82
|
+
-> build a four-slide storyboard
|
|
83
|
+
-> export HTML, PPTX, MP4, or JSON
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The default storyboard contains:
|
|
87
|
+
|
|
88
|
+
1. **Project idea** — README-based project description
|
|
89
|
+
2. **How it works** — languages, source files, and dependencies
|
|
90
|
+
3. **Evidence** — tests, public Python symbols, and contributors
|
|
91
|
+
4. **Demo** — commands found in the README and next steps
|
|
92
|
+
|
|
93
|
+
## Python API
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from reposhowcase import analyze_repository, build_storyboard
|
|
97
|
+
from reposhowcase.exporters import export_html
|
|
98
|
+
|
|
99
|
+
report = analyze_repository("./my-project")
|
|
100
|
+
storyboard = build_storyboard(report, duration=60)
|
|
101
|
+
export_html(storyboard, "showcase.html")
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
The analyzer deliberately reports unknown languages and generic file evidence
|
|
105
|
+
instead of guessing unsupported details. The CLI also supports `json`, which
|
|
106
|
+
writes the analyzed report as machine-readable data.
|
|
107
|
+
|
|
108
|
+
## Team
|
|
109
|
+
|
|
110
|
+
RepoShowcase is developed by a six-member student project team for the
|
|
111
|
+
CS1302A Agentic Coding project. Team member names will be added before the
|
|
112
|
+
public project release.
|
|
113
|
+
|
|
114
|
+
## Development
|
|
115
|
+
|
|
116
|
+
The package uses a `src/` layout. Run the focused checks from the repository
|
|
117
|
+
root:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
python -m pytest
|
|
121
|
+
python -m ruff check src tests
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The public repository will contain the project history, documentation,
|
|
125
|
+
automated checks, and release information. Personal contact details and
|
|
126
|
+
student IDs should not be included in the package.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# RepoShowcase
|
|
2
|
+
|
|
3
|
+
RepoShowcase analyzes an existing multi-language software repository and turns
|
|
4
|
+
verified repository facts into an evidence-based project showcase. The same
|
|
5
|
+
storyboard can be exported as an interactive HTML presentation, an editable
|
|
6
|
+
PowerPoint file, or an MP4 video.
|
|
7
|
+
|
|
8
|
+
## Motivation
|
|
9
|
+
|
|
10
|
+
Explaining an unfamiliar software project usually requires manually collecting
|
|
11
|
+
facts from its README, source tree, dependency files, tests, and Git history.
|
|
12
|
+
That process is slow and can lead to claims that are difficult to verify.
|
|
13
|
+
RepoShowcase automates the evidence-gathering stage while keeping each claim
|
|
14
|
+
linked to the repository-relative path that supports it.
|
|
15
|
+
|
|
16
|
+
## What makes it different
|
|
17
|
+
|
|
18
|
+
- **Evidence before presentation:** generated claims come from files and Git
|
|
19
|
+
data found in the repository rather than invented project descriptions.
|
|
20
|
+
- **One model, three outputs:** HTML, PPTX, and MP4 are generated from the same
|
|
21
|
+
storyboard, so the formats remain consistent.
|
|
22
|
+
- **Multi-language fallback:** common languages receive file-level detection,
|
|
23
|
+
while unfamiliar file types still contribute generic repository evidence.
|
|
24
|
+
- **Useful for existing projects:** the target repository does not need to
|
|
25
|
+
follow a special framework or add RepoShowcase-specific annotations.
|
|
26
|
+
|
|
27
|
+
## Example use cases
|
|
28
|
+
|
|
29
|
+
- Give a one-minute overview of a team software project.
|
|
30
|
+
- Create a review presentation for an unfamiliar open-source repository.
|
|
31
|
+
- Produce an evidence-backed project summary for a class demonstration.
|
|
32
|
+
- Generate a machine-readable JSON report for later analysis or tooling.
|
|
33
|
+
|
|
34
|
+
## Quick start
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install -e ".[dev]"
|
|
38
|
+
reposhowcase build ./my-project --outputs html,pptx,mp4 --output showcase/
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
HTML is dependency-free. PPTX requires the `pptx` extra, and MP4 requires an
|
|
42
|
+
`ffmpeg` executable on `PATH`.
|
|
43
|
+
|
|
44
|
+
After the first PyPI release, the installation command will become:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install reposhowcase
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
For development with optional exporters and test tools:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install -e ".[dev,pptx]"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Analysis pipeline
|
|
57
|
+
|
|
58
|
+
```text
|
|
59
|
+
scan repository
|
|
60
|
+
-> detect languages and project files
|
|
61
|
+
-> extract README, dependency, test, symbol, and Git evidence
|
|
62
|
+
-> build a four-slide storyboard
|
|
63
|
+
-> export HTML, PPTX, MP4, or JSON
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The default storyboard contains:
|
|
67
|
+
|
|
68
|
+
1. **Project idea** — README-based project description
|
|
69
|
+
2. **How it works** — languages, source files, and dependencies
|
|
70
|
+
3. **Evidence** — tests, public Python symbols, and contributors
|
|
71
|
+
4. **Demo** — commands found in the README and next steps
|
|
72
|
+
|
|
73
|
+
## Python API
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from reposhowcase import analyze_repository, build_storyboard
|
|
77
|
+
from reposhowcase.exporters import export_html
|
|
78
|
+
|
|
79
|
+
report = analyze_repository("./my-project")
|
|
80
|
+
storyboard = build_storyboard(report, duration=60)
|
|
81
|
+
export_html(storyboard, "showcase.html")
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The analyzer deliberately reports unknown languages and generic file evidence
|
|
85
|
+
instead of guessing unsupported details. The CLI also supports `json`, which
|
|
86
|
+
writes the analyzed report as machine-readable data.
|
|
87
|
+
|
|
88
|
+
## Team
|
|
89
|
+
|
|
90
|
+
RepoShowcase is developed by a six-member student project team for the
|
|
91
|
+
CS1302A Agentic Coding project. Team member names will be added before the
|
|
92
|
+
public project release.
|
|
93
|
+
|
|
94
|
+
## Development
|
|
95
|
+
|
|
96
|
+
The package uses a `src/` layout. Run the focused checks from the repository
|
|
97
|
+
root:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
python -m pytest
|
|
101
|
+
python -m ruff check src tests
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
The public repository will contain the project history, documentation,
|
|
105
|
+
automated checks, and release information. Personal contact details and
|
|
106
|
+
student IDs should not be included in the package.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "reposhowcase"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Evidence-based showcases for multi-language software repositories."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
authors = [{name = "RepoShowcase contributors"}]
|
|
13
|
+
dependencies = ["tomli>=2; python_version < '3.11'"]
|
|
14
|
+
|
|
15
|
+
[project.urls]
|
|
16
|
+
Homepage = "https://github.com/OttoHui/reposhowcase"
|
|
17
|
+
Repository = "https://github.com/OttoHui/reposhowcase"
|
|
18
|
+
Issues = "https://github.com/OttoHui/reposhowcase/issues"
|
|
19
|
+
|
|
20
|
+
[project.optional-dependencies]
|
|
21
|
+
pptx = ["python-pptx>=0.6.23"]
|
|
22
|
+
dev = ["pytest>=8", "ruff>=0.6"]
|
|
23
|
+
|
|
24
|
+
[project.scripts]
|
|
25
|
+
reposhowcase = "reposhowcase.cli:main"
|
|
26
|
+
|
|
27
|
+
[tool.setuptools.packages.find]
|
|
28
|
+
where = ["src"]
|
|
29
|
+
|
|
30
|
+
[tool.pytest.ini_options]
|
|
31
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Public API for evidence-based repository showcases."""
|
|
2
|
+
|
|
3
|
+
from .analyzer import analyze_repository
|
|
4
|
+
from .models import Evidence, RepositoryReport, Slide, Storyboard
|
|
5
|
+
from .storyboard import build_storyboard
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"Evidence",
|
|
9
|
+
"RepositoryReport",
|
|
10
|
+
"Slide",
|
|
11
|
+
"Storyboard",
|
|
12
|
+
"analyze_repository",
|
|
13
|
+
"build_storyboard",
|
|
14
|
+
]
|
|
15
|
+
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
import json
|
|
5
|
+
import re
|
|
6
|
+
import subprocess
|
|
7
|
+
from collections import Counter
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
import tomllib
|
|
12
|
+
except ModuleNotFoundError: # Python 3.10 compatibility
|
|
13
|
+
import tomli as tomllib
|
|
14
|
+
|
|
15
|
+
from .models import Evidence, RepositoryReport
|
|
16
|
+
|
|
17
|
+
LANGUAGES = {
|
|
18
|
+
".py": "Python", ".js": "JavaScript", ".jsx": "JavaScript",
|
|
19
|
+
".ts": "TypeScript", ".tsx": "TypeScript", ".go": "Go",
|
|
20
|
+
".rs": "Rust", ".java": "Java", ".kt": "Kotlin", ".rb": "Ruby",
|
|
21
|
+
".php": "PHP", ".cs": "C#", ".cpp": "C++", ".c": "C",
|
|
22
|
+
".swift": "Swift", ".vue": "Vue", ".html": "HTML", ".css": "CSS",
|
|
23
|
+
}
|
|
24
|
+
IGNORED = {".git", ".venv", "venv", "node_modules", "__pycache__", "dist", "build"}
|
|
25
|
+
MANIFESTS = {
|
|
26
|
+
"pyproject.toml", "requirements.txt", "package.json", "go.mod",
|
|
27
|
+
"Cargo.toml", "pom.xml", "Gemfile", "composer.json",
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _files(root: Path) -> list[Path]:
|
|
32
|
+
return [
|
|
33
|
+
path for path in root.rglob("*")
|
|
34
|
+
if path.is_file() and not any(part in IGNORED for part in path.parts)
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _python_symbols(path: Path) -> int:
|
|
39
|
+
try:
|
|
40
|
+
tree = ast.parse(path.read_text(encoding="utf-8", errors="ignore"))
|
|
41
|
+
except (OSError, SyntaxError):
|
|
42
|
+
return 0
|
|
43
|
+
return sum(
|
|
44
|
+
isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef))
|
|
45
|
+
and not node.name.startswith("_")
|
|
46
|
+
for node in ast.walk(tree)
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _git_contributors(root: Path) -> int:
|
|
51
|
+
try:
|
|
52
|
+
result = subprocess.run(
|
|
53
|
+
["git", "-C", str(root), "shortlog", "-sne", "HEAD"],
|
|
54
|
+
check=False, capture_output=True, text=True, timeout=5,
|
|
55
|
+
)
|
|
56
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
57
|
+
return 0
|
|
58
|
+
return len([line for line in result.stdout.splitlines() if line.strip()])
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _dependencies(path: Path) -> list[str]:
|
|
62
|
+
text = path.read_text(encoding="utf-8", errors="ignore")
|
|
63
|
+
if path.name == "package.json":
|
|
64
|
+
try:
|
|
65
|
+
package = json.loads(text)
|
|
66
|
+
except json.JSONDecodeError:
|
|
67
|
+
return []
|
|
68
|
+
if not isinstance(package, dict):
|
|
69
|
+
return []
|
|
70
|
+
sections = ("dependencies", "devDependencies", "peerDependencies", "optionalDependencies")
|
|
71
|
+
return sorted({
|
|
72
|
+
name for section in sections
|
|
73
|
+
for name in package.get(section, {})
|
|
74
|
+
if isinstance(name, str)
|
|
75
|
+
})
|
|
76
|
+
if path.name in {"requirements.txt", "Gemfile"}:
|
|
77
|
+
names = set()
|
|
78
|
+
for line in text.splitlines():
|
|
79
|
+
line = line.strip()
|
|
80
|
+
if not line or line.startswith(("#", "-", "git+", "http")):
|
|
81
|
+
continue
|
|
82
|
+
names.add(re.split(r"[<>=!~;\[\s]", line, maxsplit=1)[0])
|
|
83
|
+
return sorted(names)
|
|
84
|
+
if path.name == "pyproject.toml":
|
|
85
|
+
try:
|
|
86
|
+
project = tomllib.loads(text).get("project", {})
|
|
87
|
+
except tomllib.TOMLDecodeError:
|
|
88
|
+
return []
|
|
89
|
+
return sorted({
|
|
90
|
+
re.split(r"[<>=!~;\[\s]", requirement, maxsplit=1)[0]
|
|
91
|
+
for requirement in project.get("dependencies", [])
|
|
92
|
+
if isinstance(requirement, str)
|
|
93
|
+
})
|
|
94
|
+
return sorted(set(re.findall(r'(?m)^\s*([A-Za-z][A-Za-z0-9_.-]+)\s*=', text)))[:30]
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def analyze_repository(repository: str | Path) -> RepositoryReport:
|
|
98
|
+
"""Scan *repository* and return facts with paths that support them."""
|
|
99
|
+
root = Path(repository).expanduser().resolve()
|
|
100
|
+
if not root.is_dir():
|
|
101
|
+
raise NotADirectoryError(f"Repository directory does not exist: {root}")
|
|
102
|
+
paths = _files(root)
|
|
103
|
+
counts = Counter(LANGUAGES[path.suffix.lower()] for path in paths if path.suffix.lower() in LANGUAGES)
|
|
104
|
+
languages = [name for name, _ in counts.most_common()]
|
|
105
|
+
source_paths = [path for path in paths if path.suffix.lower() in LANGUAGES]
|
|
106
|
+
test_paths = [path for path in paths if "test" in path.name.lower() or "tests" in path.parts]
|
|
107
|
+
evidence: list[Evidence] = []
|
|
108
|
+
|
|
109
|
+
readme = next((p for p in paths if p.name.lower() in {"readme.md", "readme.rst", "readme.txt"}), None)
|
|
110
|
+
excerpt = ""
|
|
111
|
+
if readme:
|
|
112
|
+
readme_text = readme.read_text(encoding="utf-8", errors="ignore").strip()
|
|
113
|
+
excerpt = readme_text[:500]
|
|
114
|
+
evidence.append(Evidence(str(readme.relative_to(root)), "documentation", "Project README"))
|
|
115
|
+
for path in paths:
|
|
116
|
+
if path.name in MANIFESTS:
|
|
117
|
+
deps = _dependencies(path)
|
|
118
|
+
evidence.append(Evidence(str(path.relative_to(root)), "manifest", f"{len(deps)} declared dependencies"))
|
|
119
|
+
for path in test_paths[:10]:
|
|
120
|
+
evidence.append(Evidence(str(path.relative_to(root)), "tests", "Test source"))
|
|
121
|
+
for path in source_paths[:10]:
|
|
122
|
+
evidence.append(Evidence(str(path.relative_to(root)), "source", f"{LANGUAGES[path.suffix.lower()]} source"))
|
|
123
|
+
|
|
124
|
+
commands = re.findall(r"(?m)^\s*(?:\$|>)\s*(.+)$", readme_text) if readme else []
|
|
125
|
+
return RepositoryReport(
|
|
126
|
+
root=root, name=root.name, languages=languages,
|
|
127
|
+
source_files=len(source_paths), test_files=len(test_paths),
|
|
128
|
+
public_symbols=sum(_python_symbols(p) for p in source_paths if p.suffix == ".py"),
|
|
129
|
+
dependencies=sorted({dep for path in paths if path.name in MANIFESTS for dep in _dependencies(path)}),
|
|
130
|
+
contributors=_git_contributors(root), evidence=evidence,
|
|
131
|
+
commands=commands[:8], readme_excerpt=excerpt,
|
|
132
|
+
)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from .analyzer import analyze_repository
|
|
8
|
+
from .exporters.html import export_html
|
|
9
|
+
from .exporters.mp4 import export_mp4
|
|
10
|
+
from .exporters.pptx import export_pptx
|
|
11
|
+
from .storyboard import build_storyboard
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def main(argv: list[str] | None = None) -> int:
|
|
15
|
+
parser = argparse.ArgumentParser(prog="reposhowcase")
|
|
16
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
17
|
+
build = sub.add_parser("build", help="Analyze a repository and export a showcase")
|
|
18
|
+
build.add_argument("repository", type=Path)
|
|
19
|
+
build.add_argument("--outputs", default="html", help="Comma-separated: html,pptx,mp4,json")
|
|
20
|
+
build.add_argument("--duration", type=float, default=60)
|
|
21
|
+
build.add_argument("--output", type=Path, default=Path("showcase"))
|
|
22
|
+
args = parser.parse_args(argv)
|
|
23
|
+
report = analyze_repository(args.repository)
|
|
24
|
+
storyboard = build_storyboard(report, args.duration)
|
|
25
|
+
args.output.mkdir(parents=True, exist_ok=True)
|
|
26
|
+
outputs = {item.strip().lower() for item in args.outputs.split(",")}
|
|
27
|
+
if "html" in outputs:
|
|
28
|
+
export_html(storyboard, args.output / "showcase.html")
|
|
29
|
+
if "pptx" in outputs:
|
|
30
|
+
export_pptx(storyboard, args.output / "showcase.pptx")
|
|
31
|
+
if "mp4" in outputs:
|
|
32
|
+
export_mp4(storyboard, args.output / "showcase.mp4")
|
|
33
|
+
if "json" in outputs:
|
|
34
|
+
(args.output / "report.json").write_text(json.dumps(report.to_dict(), indent=2), encoding="utf-8")
|
|
35
|
+
print(f"Analyzed {report.name}: {len(storyboard.slides)} slides written to {args.output}")
|
|
36
|
+
return 0
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import html
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from ..models import Storyboard
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def export_html(storyboard: Storyboard, output: str | Path) -> Path:
|
|
10
|
+
"""Write a self-contained, keyboard-navigable HTML presentation."""
|
|
11
|
+
slides = []
|
|
12
|
+
for slide in storyboard.slides:
|
|
13
|
+
bullets = "".join(f"<li>{html.escape(item)}</li>" for item in slide.bullets)
|
|
14
|
+
sources = "".join(
|
|
15
|
+
f'<small class="source">{html.escape(item.path)} — {html.escape(item.detail)}</small>'
|
|
16
|
+
for item in slide.evidence
|
|
17
|
+
)
|
|
18
|
+
slides.append(
|
|
19
|
+
f'<article class="slide"><small>{slide.number:02d} / {len(storyboard.slides):02d}</small>'
|
|
20
|
+
f"<h2>{html.escape(slide.title)}</h2><p>{html.escape(slide.body)}</p>"
|
|
21
|
+
f"<ul>{bullets}</ul><div>{sources}</div></article>"
|
|
22
|
+
)
|
|
23
|
+
content = f"""<!doctype html><meta charset="utf-8"><meta name="viewport" content="width=device-width">
|
|
24
|
+
<title>{html.escape(storyboard.title)}</title>
|
|
25
|
+
<style>
|
|
26
|
+
body{{margin:0;background:#f7f9fc;color:#162033;font:16px/1.5 system-ui,-apple-system,sans-serif}}
|
|
27
|
+
header{{padding:42px 24px;color:white;background:linear-gradient(120deg,#173b8f,#2864e8 55%,#7654d6)}}
|
|
28
|
+
header h1{{max-width:900px;margin:0 auto;font-size:clamp(2rem,5vw,4rem)}} main{{max-width:900px;margin:auto;padding:32px 24px}}
|
|
29
|
+
.slide{{display:none;background:white;border:1px solid #dbe2ef;border-radius:16px;padding:32px;min-height:360px;box-shadow:0 5px 18px #243d7110}}
|
|
30
|
+
.slide.active{{display:block}} h2{{font-size:2.5rem;color:#2864e8}} li{{margin:14px 0}} .source{{display:block;color:#63708a;margin-top:8px}}
|
|
31
|
+
button{{padding:10px 16px;border:0;border-radius:8px;background:#2864e8;color:white;cursor:pointer;margin-right:8px}}
|
|
32
|
+
button:focus-visible{{outline:3px solid #7654d6;outline-offset:2px}}
|
|
33
|
+
@media (prefers-reduced-motion: reduce){{*{{scroll-behavior:auto!important}}}}
|
|
34
|
+
</style><header><h1>{html.escape(storyboard.title)}</h1></header><main><section id="slides" aria-live="polite">{"".join(slides)}</section>
|
|
35
|
+
<p><button id="prev">Previous</button><button id="next">Next</button><span id="count"></span></p>
|
|
36
|
+
<script>let i=0,s=[...document.querySelectorAll('.slide')],count=document.querySelector('#count');function show(){{s.forEach((x,n)=>x.classList.toggle('active',n===i));count.textContent=` ${{i+1}} / ${{s.length}}`;}}document.querySelector('#next').onclick=()=>{{i=(i+1)%s.length;show()}};document.querySelector('#prev').onclick=()=>{{i=(i+s.length-1)%s.length;show()}};onkeydown=e=>{{if(e.key==='ArrowRight')document.querySelector('#next').click();if(e.key==='ArrowLeft')document.querySelector('#prev').click()}};show();</script></main>"""
|
|
37
|
+
destination = Path(output)
|
|
38
|
+
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
39
|
+
destination.write_text(content, encoding="utf-8")
|
|
40
|
+
return destination
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import shutil
|
|
4
|
+
import subprocess
|
|
5
|
+
import tempfile
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from ..models import Storyboard
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def export_mp4(storyboard: Storyboard, output: str | Path) -> Path:
|
|
12
|
+
"""Render a storyboard to a simple, portable H.264 slide video."""
|
|
13
|
+
ffmpeg = shutil.which("ffmpeg")
|
|
14
|
+
if not ffmpeg:
|
|
15
|
+
raise RuntimeError("MP4 export requires ffmpeg installed and available on PATH.")
|
|
16
|
+
destination = Path(output)
|
|
17
|
+
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
18
|
+
with tempfile.TemporaryDirectory(prefix="reposhowcase-") as temp:
|
|
19
|
+
temp_path = Path(temp)
|
|
20
|
+
clips: list[Path] = []
|
|
21
|
+
for index, slide in enumerate(storyboard.slides):
|
|
22
|
+
text_file = temp_path / f"{index}.txt"
|
|
23
|
+
text_file.write_text(
|
|
24
|
+
f"{slide.title}\n\n{slide.body}\n\n"
|
|
25
|
+
+ "\n".join(f"- {bullet}" for bullet in slide.bullets),
|
|
26
|
+
encoding="utf-8",
|
|
27
|
+
)
|
|
28
|
+
clip = temp_path / f"{index}.mp4"
|
|
29
|
+
_run_ffmpeg(
|
|
30
|
+
ffmpeg,
|
|
31
|
+
"-y", "-loglevel", "error",
|
|
32
|
+
"-f", "lavfi", "-i", "color=c=173b8f:s=1280x720:r=30",
|
|
33
|
+
"-t", str(slide.seconds), "-vf",
|
|
34
|
+
f"drawtext=textfile={text_file}:fontcolor=white:fontsize=40:"
|
|
35
|
+
"line_spacing=14:x=70:y=150",
|
|
36
|
+
"-c:v", "libx264", "-pix_fmt", "yuv420p", str(clip),
|
|
37
|
+
)
|
|
38
|
+
clips.append(clip)
|
|
39
|
+
concat = temp_path / "concat.txt"
|
|
40
|
+
concat.write_text(
|
|
41
|
+
"".join(f"file '{clip.as_posix()}'\n" for clip in clips),
|
|
42
|
+
encoding="utf-8",
|
|
43
|
+
)
|
|
44
|
+
_run_ffmpeg(
|
|
45
|
+
ffmpeg, "-y", "-loglevel", "error", "-f", "concat", "-safe", "0",
|
|
46
|
+
"-i", str(concat), "-c", "copy", str(destination),
|
|
47
|
+
)
|
|
48
|
+
return destination
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _run_ffmpeg(ffmpeg: str, *arguments: str) -> None:
|
|
52
|
+
try:
|
|
53
|
+
subprocess.run([ffmpeg, *arguments], check=True, capture_output=True, text=True)
|
|
54
|
+
except subprocess.CalledProcessError as exc:
|
|
55
|
+
detail = (exc.stderr or "ffmpeg failed").strip()
|
|
56
|
+
raise RuntimeError(f"MP4 export failed: {detail}") from exc
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from ..models import Storyboard
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def export_pptx(storyboard: Storyboard, output: str | Path) -> Path:
|
|
9
|
+
try:
|
|
10
|
+
from pptx import Presentation
|
|
11
|
+
except ImportError as exc:
|
|
12
|
+
raise RuntimeError("PPTX export requires `pip install reposhowcase[pptx]`.") from exc
|
|
13
|
+
destination = Path(output)
|
|
14
|
+
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
15
|
+
presentation = Presentation()
|
|
16
|
+
for slide_data in storyboard.slides:
|
|
17
|
+
slide = presentation.slides.add_slide(presentation.slide_layouts[1])
|
|
18
|
+
slide.shapes.title.text = slide_data.title
|
|
19
|
+
frame = slide.placeholders[1].text_frame
|
|
20
|
+
frame.text = slide_data.body
|
|
21
|
+
for bullet in slide_data.bullets:
|
|
22
|
+
paragraph = frame.add_paragraph()
|
|
23
|
+
paragraph.text = bullet
|
|
24
|
+
paragraph.level = 0
|
|
25
|
+
notes = slide.notes_slide.notes_text_frame
|
|
26
|
+
notes.text = "Evidence: " + ", ".join(item.path for item in slide_data.evidence)
|
|
27
|
+
presentation.save(destination)
|
|
28
|
+
return destination
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass(frozen=True)
|
|
8
|
+
class Evidence:
|
|
9
|
+
"""A claim-supporting repository location and a short extracted detail."""
|
|
10
|
+
|
|
11
|
+
path: str
|
|
12
|
+
kind: str
|
|
13
|
+
detail: str
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class RepositoryReport:
|
|
18
|
+
root: Path
|
|
19
|
+
name: str
|
|
20
|
+
languages: list[str]
|
|
21
|
+
source_files: int
|
|
22
|
+
test_files: int
|
|
23
|
+
public_symbols: int
|
|
24
|
+
dependencies: list[str]
|
|
25
|
+
contributors: int
|
|
26
|
+
evidence: list[Evidence] = field(default_factory=list)
|
|
27
|
+
commands: list[str] = field(default_factory=list)
|
|
28
|
+
readme_excerpt: str = ""
|
|
29
|
+
|
|
30
|
+
def to_dict(self) -> dict[str, object]:
|
|
31
|
+
return {
|
|
32
|
+
"name": self.name,
|
|
33
|
+
"languages": self.languages,
|
|
34
|
+
"source_files": self.source_files,
|
|
35
|
+
"test_files": self.test_files,
|
|
36
|
+
"public_symbols": self.public_symbols,
|
|
37
|
+
"dependencies": self.dependencies,
|
|
38
|
+
"contributors": self.contributors,
|
|
39
|
+
"evidence": [e.__dict__ for e in self.evidence],
|
|
40
|
+
"commands": self.commands,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass(frozen=True)
|
|
45
|
+
class Slide:
|
|
46
|
+
number: int
|
|
47
|
+
title: str
|
|
48
|
+
body: str
|
|
49
|
+
bullets: tuple[str, ...] = ()
|
|
50
|
+
evidence: tuple[Evidence, ...] = ()
|
|
51
|
+
seconds: float = 0
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass(frozen=True)
|
|
55
|
+
class Storyboard:
|
|
56
|
+
title: str
|
|
57
|
+
slides: tuple[Slide, ...]
|
|
58
|
+
duration: float
|
|
59
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from .models import RepositoryReport, Slide, Storyboard
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def build_storyboard(report: RepositoryReport, duration: float = 60) -> Storyboard:
|
|
7
|
+
if duration <= 0:
|
|
8
|
+
raise ValueError("duration must be greater than zero")
|
|
9
|
+
durations = [duration * ratio for ratio in (0.2, 0.3, 0.3, 0.2)]
|
|
10
|
+
evidence = tuple(report.evidence)
|
|
11
|
+
return Storyboard(
|
|
12
|
+
title=f"{report.name}: repository showcase",
|
|
13
|
+
duration=duration,
|
|
14
|
+
slides=(
|
|
15
|
+
Slide(1, "Project idea", "What the repository is built to do.",
|
|
16
|
+
(report.readme_excerpt or "No README evidence found.",), evidence[:2], durations[0]),
|
|
17
|
+
Slide(2, "How it works", "Languages, source files, and dependencies.",
|
|
18
|
+
(f"Languages: {', '.join(report.languages) or 'Unknown'}",
|
|
19
|
+
f"{report.source_files} source files and {len(report.dependencies)} dependencies"),
|
|
20
|
+
tuple(e for e in evidence if e.kind in {"source", "manifest"}), durations[1]),
|
|
21
|
+
Slide(3, "Evidence", "Quality signals found in the repository.",
|
|
22
|
+
(f"{report.test_files} test files", f"{report.public_symbols} public Python symbols",
|
|
23
|
+
f"{report.contributors or 'No'} Git contributors detected"),
|
|
24
|
+
tuple(e for e in evidence if e.kind in {"tests", "documentation"}), durations[2]),
|
|
25
|
+
Slide(4, "Demo", "Commands and next steps.",
|
|
26
|
+
tuple(report.commands or ("Inspect the generated evidence report.",)),
|
|
27
|
+
evidence[-3:], durations[3]),
|
|
28
|
+
),
|
|
29
|
+
)
|
|
30
|
+
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: reposhowcase
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Evidence-based showcases for multi-language software repositories.
|
|
5
|
+
Author: RepoShowcase contributors
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/OttoHui/reposhowcase
|
|
8
|
+
Project-URL: Repository, https://github.com/OttoHui/reposhowcase
|
|
9
|
+
Project-URL: Issues, https://github.com/OttoHui/reposhowcase/issues
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: tomli>=2; python_version < "3.11"
|
|
14
|
+
Provides-Extra: pptx
|
|
15
|
+
Requires-Dist: python-pptx>=0.6.23; extra == "pptx"
|
|
16
|
+
Provides-Extra: dev
|
|
17
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
18
|
+
Requires-Dist: ruff>=0.6; extra == "dev"
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
# RepoShowcase
|
|
22
|
+
|
|
23
|
+
RepoShowcase analyzes an existing multi-language software repository and turns
|
|
24
|
+
verified repository facts into an evidence-based project showcase. The same
|
|
25
|
+
storyboard can be exported as an interactive HTML presentation, an editable
|
|
26
|
+
PowerPoint file, or an MP4 video.
|
|
27
|
+
|
|
28
|
+
## Motivation
|
|
29
|
+
|
|
30
|
+
Explaining an unfamiliar software project usually requires manually collecting
|
|
31
|
+
facts from its README, source tree, dependency files, tests, and Git history.
|
|
32
|
+
That process is slow and can lead to claims that are difficult to verify.
|
|
33
|
+
RepoShowcase automates the evidence-gathering stage while keeping each claim
|
|
34
|
+
linked to the repository-relative path that supports it.
|
|
35
|
+
|
|
36
|
+
## What makes it different
|
|
37
|
+
|
|
38
|
+
- **Evidence before presentation:** generated claims come from files and Git
|
|
39
|
+
data found in the repository rather than invented project descriptions.
|
|
40
|
+
- **One model, three outputs:** HTML, PPTX, and MP4 are generated from the same
|
|
41
|
+
storyboard, so the formats remain consistent.
|
|
42
|
+
- **Multi-language fallback:** common languages receive file-level detection,
|
|
43
|
+
while unfamiliar file types still contribute generic repository evidence.
|
|
44
|
+
- **Useful for existing projects:** the target repository does not need to
|
|
45
|
+
follow a special framework or add RepoShowcase-specific annotations.
|
|
46
|
+
|
|
47
|
+
## Example use cases
|
|
48
|
+
|
|
49
|
+
- Give a one-minute overview of a team software project.
|
|
50
|
+
- Create a review presentation for an unfamiliar open-source repository.
|
|
51
|
+
- Produce an evidence-backed project summary for a class demonstration.
|
|
52
|
+
- Generate a machine-readable JSON report for later analysis or tooling.
|
|
53
|
+
|
|
54
|
+
## Quick start
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install -e ".[dev]"
|
|
58
|
+
reposhowcase build ./my-project --outputs html,pptx,mp4 --output showcase/
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
HTML is dependency-free. PPTX requires the `pptx` extra, and MP4 requires an
|
|
62
|
+
`ffmpeg` executable on `PATH`.
|
|
63
|
+
|
|
64
|
+
After the first PyPI release, the installation command will become:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pip install reposhowcase
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
For development with optional exporters and test tools:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pip install -e ".[dev,pptx]"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Analysis pipeline
|
|
77
|
+
|
|
78
|
+
```text
|
|
79
|
+
scan repository
|
|
80
|
+
-> detect languages and project files
|
|
81
|
+
-> extract README, dependency, test, symbol, and Git evidence
|
|
82
|
+
-> build a four-slide storyboard
|
|
83
|
+
-> export HTML, PPTX, MP4, or JSON
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The default storyboard contains:
|
|
87
|
+
|
|
88
|
+
1. **Project idea** — README-based project description
|
|
89
|
+
2. **How it works** — languages, source files, and dependencies
|
|
90
|
+
3. **Evidence** — tests, public Python symbols, and contributors
|
|
91
|
+
4. **Demo** — commands found in the README and next steps
|
|
92
|
+
|
|
93
|
+
## Python API
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from reposhowcase import analyze_repository, build_storyboard
|
|
97
|
+
from reposhowcase.exporters import export_html
|
|
98
|
+
|
|
99
|
+
report = analyze_repository("./my-project")
|
|
100
|
+
storyboard = build_storyboard(report, duration=60)
|
|
101
|
+
export_html(storyboard, "showcase.html")
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
The analyzer deliberately reports unknown languages and generic file evidence
|
|
105
|
+
instead of guessing unsupported details. The CLI also supports `json`, which
|
|
106
|
+
writes the analyzed report as machine-readable data.
|
|
107
|
+
|
|
108
|
+
## Team
|
|
109
|
+
|
|
110
|
+
RepoShowcase is developed by a six-member student project team for the
|
|
111
|
+
CS1302A Agentic Coding project. Team member names will be added before the
|
|
112
|
+
public project release.
|
|
113
|
+
|
|
114
|
+
## Development
|
|
115
|
+
|
|
116
|
+
The package uses a `src/` layout. Run the focused checks from the repository
|
|
117
|
+
root:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
python -m pytest
|
|
121
|
+
python -m ruff check src tests
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The public repository will contain the project history, documentation,
|
|
125
|
+
automated checks, and release information. Personal contact details and
|
|
126
|
+
student IDs should not be included in the package.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/reposhowcase/__init__.py
|
|
5
|
+
src/reposhowcase/analyzer.py
|
|
6
|
+
src/reposhowcase/cli.py
|
|
7
|
+
src/reposhowcase/models.py
|
|
8
|
+
src/reposhowcase/storyboard.py
|
|
9
|
+
src/reposhowcase.egg-info/PKG-INFO
|
|
10
|
+
src/reposhowcase.egg-info/SOURCES.txt
|
|
11
|
+
src/reposhowcase.egg-info/dependency_links.txt
|
|
12
|
+
src/reposhowcase.egg-info/entry_points.txt
|
|
13
|
+
src/reposhowcase.egg-info/requires.txt
|
|
14
|
+
src/reposhowcase.egg-info/top_level.txt
|
|
15
|
+
src/reposhowcase/exporters/__init__.py
|
|
16
|
+
src/reposhowcase/exporters/html.py
|
|
17
|
+
src/reposhowcase/exporters/mp4.py
|
|
18
|
+
src/reposhowcase/exporters/pptx.py
|
|
19
|
+
tests/test_reposhowcase.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
reposhowcase
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from reposhowcase import analyze_repository, build_storyboard
|
|
4
|
+
from reposhowcase.exporters.html import export_html
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_analyze_and_export(tmp_path: Path) -> None:
|
|
8
|
+
(tmp_path / "README.md").write_text("# Demo\nRun it with `$ demo run`.\n")
|
|
9
|
+
(tmp_path / "main.py").write_text("def public_api():\n return 1\n")
|
|
10
|
+
(tmp_path / "tests").mkdir()
|
|
11
|
+
(tmp_path / "tests" / "test_main.py").write_text("def test_it(): pass\n")
|
|
12
|
+
|
|
13
|
+
report = analyze_repository(tmp_path)
|
|
14
|
+
assert report.languages == ["Python"]
|
|
15
|
+
assert report.source_files == 2
|
|
16
|
+
assert report.test_files == 1
|
|
17
|
+
assert report.public_symbols == 2
|
|
18
|
+
storyboard = build_storyboard(report)
|
|
19
|
+
destination = export_html(storyboard, tmp_path / "showcase.html")
|
|
20
|
+
assert destination.read_text().count("class=\"slide\"") == 4
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_manifest_dependencies_and_readme_commands(tmp_path: Path) -> None:
|
|
24
|
+
(tmp_path / "README.md").write_text("# Demo\n$ python -m demo\n")
|
|
25
|
+
(tmp_path / "pyproject.toml").write_text(
|
|
26
|
+
"[project]\n"
|
|
27
|
+
'dependencies = ["requests>=2", "rich"]\n'
|
|
28
|
+
)
|
|
29
|
+
(tmp_path / "package.json").write_text(
|
|
30
|
+
'{"dependencies": {"react": "^1"}, "scripts": {"build": "x"}}\n'
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
report = analyze_repository(tmp_path)
|
|
34
|
+
|
|
35
|
+
assert report.dependencies == ["react", "requests", "rich"]
|
|
36
|
+
assert report.commands == ["python -m demo"]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_invalid_repository_is_rejected(tmp_path: Path) -> None:
|
|
40
|
+
missing = tmp_path / "missing"
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
analyze_repository(missing)
|
|
44
|
+
except NotADirectoryError as exc:
|
|
45
|
+
assert str(missing) in str(exc)
|
|
46
|
+
else:
|
|
47
|
+
raise AssertionError("analyze_repository should reject a missing directory")
|