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.
@@ -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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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,9 @@
1
+ """Storyboard exporters."""
2
+
3
+ """Output adapters for the shared RepoShowcase storyboard."""
4
+
5
+ from .html import export_html
6
+ from .mp4 import export_mp4
7
+ from .pptx import export_pptx
8
+
9
+ __all__ = ["export_html", "export_mp4", "export_pptx"]
@@ -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,2 @@
1
+ [console_scripts]
2
+ reposhowcase = reposhowcase.cli:main
@@ -0,0 +1,10 @@
1
+
2
+ [:python_version < "3.11"]
3
+ tomli>=2
4
+
5
+ [dev]
6
+ pytest>=8
7
+ ruff>=0.6
8
+
9
+ [pptx]
10
+ python-pptx>=0.6.23
@@ -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")