podojo-cli 0.8.0__tar.gz → 0.8.2__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.
- {podojo_cli-0.8.0 → podojo_cli-0.8.2}/CHANGELOG.md +10 -0
- {podojo_cli-0.8.0 → podojo_cli-0.8.2}/PKG-INFO +13 -2
- {podojo_cli-0.8.0 → podojo_cli-0.8.2}/pyproject.toml +15 -2
- {podojo_cli-0.8.0 → podojo_cli-0.8.2}/src/podojo_cli/commands/projects.py +37 -0
- podojo_cli-0.8.2/tests/test_projects.py +147 -0
- {podojo_cli-0.8.0 → podojo_cli-0.8.2}/uv.lock +1 -1
- podojo_cli-0.8.0/tests/test_projects.py +0 -72
- {podojo_cli-0.8.0 → podojo_cli-0.8.2}/.github/workflows/publish.yml +0 -0
- {podojo_cli-0.8.0 → podojo_cli-0.8.2}/.gitignore +0 -0
- {podojo_cli-0.8.0 → podojo_cli-0.8.2}/CLAUDE.md +0 -0
- {podojo_cli-0.8.0 → podojo_cli-0.8.2}/README.md +0 -0
- {podojo_cli-0.8.0 → podojo_cli-0.8.2}/src/podojo_cli/__init__.py +0 -0
- {podojo_cli-0.8.0 → podojo_cli-0.8.2}/src/podojo_cli/client.py +0 -0
- {podojo_cli-0.8.0 → podojo_cli-0.8.2}/src/podojo_cli/commands/__init__.py +0 -0
- {podojo_cli-0.8.0 → podojo_cli-0.8.2}/src/podojo_cli/commands/auth.py +0 -0
- {podojo_cli-0.8.0 → podojo_cli-0.8.2}/src/podojo_cli/commands/gdrive.py +0 -0
- {podojo_cli-0.8.0 → podojo_cli-0.8.2}/src/podojo_cli/commands/interviews.py +0 -0
- {podojo_cli-0.8.0 → podojo_cli-0.8.2}/src/podojo_cli/commands/showreel.py +0 -0
- {podojo_cli-0.8.0 → podojo_cli-0.8.2}/src/podojo_cli/commands/synth.py +0 -0
- {podojo_cli-0.8.0 → podojo_cli-0.8.2}/src/podojo_cli/commands/transcripts.py +0 -0
- {podojo_cli-0.8.0 → podojo_cli-0.8.2}/src/podojo_cli/commands/usertests.py +0 -0
- {podojo_cli-0.8.0 → podojo_cli-0.8.2}/src/podojo_cli/commands/videos.py +0 -0
- {podojo_cli-0.8.0 → podojo_cli-0.8.2}/src/podojo_cli/config.py +0 -0
- {podojo_cli-0.8.0 → podojo_cli-0.8.2}/src/podojo_cli/gdrive/__init__.py +0 -0
- {podojo_cli-0.8.0 → podojo_cli-0.8.2}/src/podojo_cli/gdrive/list.py +0 -0
- {podojo_cli-0.8.0 → podojo_cli-0.8.2}/src/podojo_cli/gdrive/upload.py +0 -0
- {podojo_cli-0.8.0 → podojo_cli-0.8.2}/src/podojo_cli/main.py +0 -0
- {podojo_cli-0.8.0 → podojo_cli-0.8.2}/src/podojo_cli/synth/__init__.py +0 -0
- {podojo_cli-0.8.0 → podojo_cli-0.8.2}/src/podojo_cli/synth/driver.py +0 -0
- {podojo_cli-0.8.0 → podojo_cli-0.8.2}/src/podojo_cli/synth/session.py +0 -0
- {podojo_cli-0.8.0 → podojo_cli-0.8.2}/src/podojo_cli/version_check.py +0 -0
- {podojo_cli-0.8.0 → podojo_cli-0.8.2}/src/podojo_cli/video/__init__.py +0 -0
- {podojo_cli-0.8.0 → podojo_cli-0.8.2}/src/podojo_cli/video/showreel.py +0 -0
- {podojo_cli-0.8.0 → podojo_cli-0.8.2}/tests/conftest.py +0 -0
- {podojo_cli-0.8.0 → podojo_cli-0.8.2}/tests/test_auth.py +0 -0
- {podojo_cli-0.8.0 → podojo_cli-0.8.2}/tests/test_gdrive.py +0 -0
- {podojo_cli-0.8.0 → podojo_cli-0.8.2}/tests/test_interviews.py +0 -0
- {podojo_cli-0.8.0 → podojo_cli-0.8.2}/tests/test_showreel.py +0 -0
- {podojo_cli-0.8.0 → podojo_cli-0.8.2}/tests/test_usertests.py +0 -0
|
@@ -5,6 +5,16 @@ All notable changes to the Podojo CLI will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org).
|
|
7
7
|
|
|
8
|
+
## [0.8.2] - 2026-05-14
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- Enriched PyPI package metadata: added `keywords`, `classifiers`, author email, and a `Changelog` project URL.
|
|
12
|
+
|
|
13
|
+
## [0.8.1] - 2026-05-13
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- `projects upload-doc` strips embedded images (base64 reference definitions, inline ``, reference-style `![][ref]`, and `<img>` tags) before upload. Google-Docs-exported markdown often inlines images as multi-MB base64 blobs; the CLI now prints the count and size reduction.
|
|
17
|
+
|
|
8
18
|
## [0.8.0] - 2026-05-13
|
|
9
19
|
|
|
10
20
|
### Added
|
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: podojo-cli
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.2
|
|
4
4
|
Summary: CLI for the Podojo user research platform
|
|
5
5
|
Project-URL: Homepage, https://github.com/podojo/cli-podojo
|
|
6
6
|
Project-URL: Source, https://github.com/podojo/cli-podojo
|
|
7
7
|
Project-URL: Issues, https://github.com/podojo/cli-podojo/issues
|
|
8
|
-
|
|
8
|
+
Project-URL: Changelog, https://github.com/podojo/cli-podojo/blob/main/CHANGELOG.md
|
|
9
|
+
Author-email: Jochen Ade <jochen.ade@podojo.com>
|
|
9
10
|
License-Expression: MIT
|
|
11
|
+
Keywords: cli,podojo,transcripts,user-research,ux-research
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
15
|
+
Classifier: Intended Audience :: Other Audience
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Utilities
|
|
10
21
|
Requires-Python: >=3.12
|
|
11
22
|
Requires-Dist: google-api-python-client>=2.0
|
|
12
23
|
Requires-Dist: google-auth>=2.0
|
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "podojo-cli"
|
|
3
|
-
version = "0.8.
|
|
3
|
+
version = "0.8.2"
|
|
4
4
|
description = "CLI for the Podojo user research platform"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
7
7
|
requires-python = ">=3.12"
|
|
8
|
-
authors = [{ name = "Jochen Ade" }]
|
|
8
|
+
authors = [{ name = "Jochen Ade", email = "jochen.ade@podojo.com" }]
|
|
9
|
+
keywords = ["podojo", "ux-research", "user-research", "cli", "transcripts"]
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Development Status :: 4 - Beta",
|
|
12
|
+
"Environment :: Console",
|
|
13
|
+
"Intended Audience :: End Users/Desktop",
|
|
14
|
+
"Intended Audience :: Other Audience",
|
|
15
|
+
"License :: OSI Approved :: MIT License",
|
|
16
|
+
"Operating System :: OS Independent",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.12",
|
|
19
|
+
"Topic :: Utilities",
|
|
20
|
+
]
|
|
9
21
|
dependencies = [
|
|
10
22
|
"typer>=0.15",
|
|
11
23
|
"httpx>=0.28",
|
|
@@ -23,6 +35,7 @@ synth = ["playwright>=1.40"]
|
|
|
23
35
|
Homepage = "https://github.com/podojo/cli-podojo"
|
|
24
36
|
Source = "https://github.com/podojo/cli-podojo"
|
|
25
37
|
Issues = "https://github.com/podojo/cli-podojo/issues"
|
|
38
|
+
Changelog = "https://github.com/podojo/cli-podojo/blob/main/CHANGELOG.md"
|
|
26
39
|
|
|
27
40
|
[project.scripts]
|
|
28
41
|
podojo = "podojo_cli.main:app"
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import re
|
|
1
2
|
from pathlib import Path
|
|
2
3
|
|
|
3
4
|
import httpx
|
|
@@ -18,6 +19,33 @@ CLI_TO_API_DOC_TYPE = {
|
|
|
18
19
|
UPLOADABLE_DOC_TYPES = {"brief", "final"}
|
|
19
20
|
MARKDOWN_SUFFIXES = {".md", ".markdown"}
|
|
20
21
|
|
|
22
|
+
# Reference-style image definition with a data: URL — `[label]: <data:image/png;base64,...>`
|
|
23
|
+
# These pack base64-encoded images into a single line and dominate file size.
|
|
24
|
+
DATA_URI_REF_DEF_RE = re.compile(
|
|
25
|
+
r"^\s*\[[^\]\n]+\]:\s+<?data:[^\n>]*>?[^\n]*\n?",
|
|
26
|
+
re.MULTILINE,
|
|
27
|
+
)
|
|
28
|
+
INLINE_IMG_RE = re.compile(r"!\[[^\]]*\]\([^)]*\)")
|
|
29
|
+
REF_IMG_RE = re.compile(r"!\[[^\]]*\]\[[^\]]*\]")
|
|
30
|
+
HTML_IMG_RE = re.compile(r"<img\b[^>]*/?>", re.IGNORECASE)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def strip_images(text: str) -> tuple[str, int]:
|
|
34
|
+
"""Remove image markdown and HTML img tags. Returns (cleaned_text, count_removed)."""
|
|
35
|
+
count = 0
|
|
36
|
+
for pattern in (DATA_URI_REF_DEF_RE, INLINE_IMG_RE, REF_IMG_RE, HTML_IMG_RE):
|
|
37
|
+
text, n = pattern.subn("", text)
|
|
38
|
+
count += n
|
|
39
|
+
return text, count
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _human_size(n: int) -> str:
|
|
43
|
+
if n >= 1024 * 1024:
|
|
44
|
+
return f"{n / 1024 / 1024:.1f} MB"
|
|
45
|
+
if n >= 1024:
|
|
46
|
+
return f"{n / 1024:.1f} KB"
|
|
47
|
+
return f"{n} B"
|
|
48
|
+
|
|
21
49
|
|
|
22
50
|
@app.command("list")
|
|
23
51
|
def list_projects(
|
|
@@ -86,6 +114,15 @@ def upload_doc(
|
|
|
86
114
|
console.print(f"[red]Error:[/red] File is not valid UTF-8 text: {file}")
|
|
87
115
|
raise typer.Exit(1)
|
|
88
116
|
|
|
117
|
+
original_size = len(content.encode("utf-8"))
|
|
118
|
+
content, image_count = strip_images(content)
|
|
119
|
+
new_size = len(content.encode("utf-8"))
|
|
120
|
+
if image_count:
|
|
121
|
+
console.print(
|
|
122
|
+
f"Stripped [bold]{image_count}[/bold] image(s) "
|
|
123
|
+
f"({_human_size(original_size)} → {_human_size(new_size)})"
|
|
124
|
+
)
|
|
125
|
+
|
|
89
126
|
api_type = CLI_TO_API_DOC_TYPE[doc_type]
|
|
90
127
|
client = PodojoClient()
|
|
91
128
|
try:
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
from podojo_cli.commands.projects import strip_images
|
|
2
|
+
from podojo_cli.main import app
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def test_list_projects(runner, httpx_mock):
|
|
6
|
+
httpx_mock.add_response(
|
|
7
|
+
url="http://test.local/api/v1/projects?skip=0&limit=50",
|
|
8
|
+
json={
|
|
9
|
+
"projects": [
|
|
10
|
+
{"name": "Alpha", "brief": "First project"},
|
|
11
|
+
{"name": "Beta", "brief": "Second project"},
|
|
12
|
+
],
|
|
13
|
+
"total": 2,
|
|
14
|
+
"skip": 0,
|
|
15
|
+
"limit": 50,
|
|
16
|
+
},
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
result = runner.invoke(app, ["projects", "list"])
|
|
20
|
+
|
|
21
|
+
assert result.exit_code == 0
|
|
22
|
+
assert "Alpha" in result.output
|
|
23
|
+
assert "Beta" in result.output
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_list_projects_empty(runner, httpx_mock):
|
|
27
|
+
httpx_mock.add_response(
|
|
28
|
+
url="http://test.local/api/v1/projects?skip=0&limit=50",
|
|
29
|
+
json={
|
|
30
|
+
"projects": [],
|
|
31
|
+
"total": 0,
|
|
32
|
+
"skip": 0,
|
|
33
|
+
"limit": 50,
|
|
34
|
+
},
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
result = runner.invoke(app, ["projects", "list"])
|
|
38
|
+
|
|
39
|
+
assert result.exit_code == 0
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_create_project(runner, httpx_mock):
|
|
43
|
+
httpx_mock.add_response(
|
|
44
|
+
method="POST",
|
|
45
|
+
url="http://test.local/api/v1/projects",
|
|
46
|
+
match_json={"name": "Gamma", "brief": "Third project"},
|
|
47
|
+
json={
|
|
48
|
+
"id": "abc123",
|
|
49
|
+
"name": "Gamma",
|
|
50
|
+
"brief": "Third project",
|
|
51
|
+
"message": "Project created successfully",
|
|
52
|
+
},
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
result = runner.invoke(app, ["projects", "create", "Gamma", "--brief", "Third project"])
|
|
56
|
+
|
|
57
|
+
assert result.exit_code == 0
|
|
58
|
+
assert "Gamma" in result.output
|
|
59
|
+
assert "abc123" in result.output
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_create_project_duplicate(runner, httpx_mock):
|
|
63
|
+
httpx_mock.add_response(
|
|
64
|
+
method="POST",
|
|
65
|
+
url="http://test.local/api/v1/projects",
|
|
66
|
+
status_code=409,
|
|
67
|
+
json={"detail": "Project with this name already exists"},
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
result = runner.invoke(app, ["projects", "create", "Gamma"])
|
|
71
|
+
|
|
72
|
+
assert result.exit_code == 1
|
|
73
|
+
assert "already exists" in result.output
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def test_strip_images_base64_reference_defs():
|
|
77
|
+
text = (
|
|
78
|
+
"# Title\n\n"
|
|
79
|
+
"Paragraph with ![][image1] inline reference.\n\n"
|
|
80
|
+
"[image1]: <data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA>\n"
|
|
81
|
+
"[image2]: <data:image/jpeg;base64,/9j/4AAQSkZJRg==>\n"
|
|
82
|
+
)
|
|
83
|
+
cleaned, count = strip_images(text)
|
|
84
|
+
assert "data:image" not in cleaned
|
|
85
|
+
assert "![]" not in cleaned
|
|
86
|
+
assert "image1" not in cleaned
|
|
87
|
+
assert "# Title" in cleaned
|
|
88
|
+
assert "Paragraph with inline reference." in cleaned
|
|
89
|
+
assert count == 3
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_strip_images_inline_and_html():
|
|
93
|
+
text = (
|
|
94
|
+
"Inline: \n"
|
|
95
|
+
'HTML: <img src="https://example.com/y.jpg" alt="y">\n'
|
|
96
|
+
"Reference: ![alt][ref]\n"
|
|
97
|
+
)
|
|
98
|
+
cleaned, count = strip_images(text)
|
|
99
|
+
assert "example.com" not in cleaned
|
|
100
|
+
assert "<img" not in cleaned
|
|
101
|
+
assert "![alt]" not in cleaned
|
|
102
|
+
assert count == 3
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def test_strip_images_keeps_regular_links():
|
|
106
|
+
text = "See [the doc](https://example.com/doc) and [ref][label]."
|
|
107
|
+
cleaned, count = strip_images(text)
|
|
108
|
+
assert cleaned == text
|
|
109
|
+
assert count == 0
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def test_upload_doc_strips_images_before_upload(runner, tmp_path, httpx_mock):
|
|
113
|
+
md = (
|
|
114
|
+
"# Brief\n\nBody text ![][image1] continues.\n\n"
|
|
115
|
+
"[image1]: <data:image/png;base64,AAAA>\n"
|
|
116
|
+
)
|
|
117
|
+
file = tmp_path / "brief.md"
|
|
118
|
+
file.write_text(md, encoding="utf-8")
|
|
119
|
+
|
|
120
|
+
httpx_mock.add_response(
|
|
121
|
+
method="PUT",
|
|
122
|
+
url="http://test.local/api/v1/projects/Alpha/documents/research_brief",
|
|
123
|
+
match_json={"content": "# Brief\n\nBody text continues.\n"},
|
|
124
|
+
json={"ok": True},
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
result = runner.invoke(app, ["projects", "upload-doc", "Alpha", str(file), "--type", "brief"])
|
|
128
|
+
|
|
129
|
+
assert result.exit_code == 0
|
|
130
|
+
assert "Stripped" in result.output
|
|
131
|
+
assert "2 image" in result.output
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def test_upload_doc_no_images_no_strip_message(runner, tmp_path, httpx_mock):
|
|
135
|
+
file = tmp_path / "brief.md"
|
|
136
|
+
file.write_text("# Brief\n\nJust text, no images.\n", encoding="utf-8")
|
|
137
|
+
|
|
138
|
+
httpx_mock.add_response(
|
|
139
|
+
method="PUT",
|
|
140
|
+
url="http://test.local/api/v1/projects/Alpha/documents/research_brief",
|
|
141
|
+
json={"ok": True},
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
result = runner.invoke(app, ["projects", "upload-doc", "Alpha", str(file), "--type", "brief"])
|
|
145
|
+
|
|
146
|
+
assert result.exit_code == 0
|
|
147
|
+
assert "Stripped" not in result.output
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
from podojo_cli.main import app
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
def test_list_projects(runner, httpx_mock):
|
|
5
|
-
httpx_mock.add_response(
|
|
6
|
-
url="http://test.local/api/v1/projects?skip=0&limit=50",
|
|
7
|
-
json={
|
|
8
|
-
"projects": [
|
|
9
|
-
{"name": "Alpha", "brief": "First project"},
|
|
10
|
-
{"name": "Beta", "brief": "Second project"},
|
|
11
|
-
],
|
|
12
|
-
"total": 2,
|
|
13
|
-
"skip": 0,
|
|
14
|
-
"limit": 50,
|
|
15
|
-
},
|
|
16
|
-
)
|
|
17
|
-
|
|
18
|
-
result = runner.invoke(app, ["projects", "list"])
|
|
19
|
-
|
|
20
|
-
assert result.exit_code == 0
|
|
21
|
-
assert "Alpha" in result.output
|
|
22
|
-
assert "Beta" in result.output
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def test_list_projects_empty(runner, httpx_mock):
|
|
26
|
-
httpx_mock.add_response(
|
|
27
|
-
url="http://test.local/api/v1/projects?skip=0&limit=50",
|
|
28
|
-
json={
|
|
29
|
-
"projects": [],
|
|
30
|
-
"total": 0,
|
|
31
|
-
"skip": 0,
|
|
32
|
-
"limit": 50,
|
|
33
|
-
},
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
result = runner.invoke(app, ["projects", "list"])
|
|
37
|
-
|
|
38
|
-
assert result.exit_code == 0
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def test_create_project(runner, httpx_mock):
|
|
42
|
-
httpx_mock.add_response(
|
|
43
|
-
method="POST",
|
|
44
|
-
url="http://test.local/api/v1/projects",
|
|
45
|
-
match_json={"name": "Gamma", "brief": "Third project"},
|
|
46
|
-
json={
|
|
47
|
-
"id": "abc123",
|
|
48
|
-
"name": "Gamma",
|
|
49
|
-
"brief": "Third project",
|
|
50
|
-
"message": "Project created successfully",
|
|
51
|
-
},
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
result = runner.invoke(app, ["projects", "create", "Gamma", "--brief", "Third project"])
|
|
55
|
-
|
|
56
|
-
assert result.exit_code == 0
|
|
57
|
-
assert "Gamma" in result.output
|
|
58
|
-
assert "abc123" in result.output
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def test_create_project_duplicate(runner, httpx_mock):
|
|
62
|
-
httpx_mock.add_response(
|
|
63
|
-
method="POST",
|
|
64
|
-
url="http://test.local/api/v1/projects",
|
|
65
|
-
status_code=409,
|
|
66
|
-
json={"detail": "Project with this name already exists"},
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
result = runner.invoke(app, ["projects", "create", "Gamma"])
|
|
70
|
-
|
|
71
|
-
assert result.exit_code == 1
|
|
72
|
-
assert "already exists" in result.output
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|