article-cli 1.3.0__tar.gz → 1.3.1__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.
- {article_cli-1.3.0 → article_cli-1.3.1}/PKG-INFO +1 -1
- {article_cli-1.3.0 → article_cli-1.3.1}/pyproject.toml +1 -1
- {article_cli-1.3.0 → article_cli-1.3.1}/src/article_cli/__init__.py +1 -1
- {article_cli-1.3.0 → article_cli-1.3.1}/src/article_cli/fonts.py +48 -29
- {article_cli-1.3.0 → article_cli-1.3.1}/tests/test_fonts.py +8 -9
- {article_cli-1.3.0 → article_cli-1.3.1}/.gitignore +0 -0
- {article_cli-1.3.0 → article_cli-1.3.1}/LICENSE +0 -0
- {article_cli-1.3.0 → article_cli-1.3.1}/README.md +0 -0
- {article_cli-1.3.0 → article_cli-1.3.1}/src/article_cli/__main__.py +0 -0
- {article_cli-1.3.0 → article_cli-1.3.1}/src/article_cli/cli.py +0 -0
- {article_cli-1.3.0 → article_cli-1.3.1}/src/article_cli/config.py +0 -0
- {article_cli-1.3.0 → article_cli-1.3.1}/src/article_cli/git_manager.py +0 -0
- {article_cli-1.3.0 → article_cli-1.3.1}/src/article_cli/latex_compiler.py +0 -0
- {article_cli-1.3.0 → article_cli-1.3.1}/src/article_cli/repository_setup.py +0 -0
- {article_cli-1.3.0 → article_cli-1.3.1}/src/article_cli/themes.py +0 -0
- {article_cli-1.3.0 → article_cli-1.3.1}/src/article_cli/zotero.py +0 -0
- {article_cli-1.3.0 → article_cli-1.3.1}/tests/__init__.py +0 -0
- {article_cli-1.3.0 → article_cli-1.3.1}/tests/test_config.py +0 -0
- {article_cli-1.3.0 → article_cli-1.3.1}/tests/test_latex_compiler.py +0 -0
- {article_cli-1.3.0 → article_cli-1.3.1}/tests/test_repository_setup.py +0 -0
- {article_cli-1.3.0 → article_cli-1.3.1}/tests/test_themes.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: article-cli
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.1
|
|
4
4
|
Summary: CLI tool for managing LaTeX articles with git integration and Zotero bibliography
|
|
5
5
|
Project-URL: Homepage, https://github.com/feelpp/article.cli
|
|
6
6
|
Project-URL: Documentation, https://github.com/feelpp/article.cli#readme
|
|
@@ -9,26 +9,38 @@ import zipfile
|
|
|
9
9
|
import tempfile
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
from typing import List, Dict, Optional, Any
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
|
|
13
|
+
import requests
|
|
14
14
|
|
|
15
15
|
from .zotero import print_error, print_info, print_success, print_warning
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
# Default font sources for common themes
|
|
19
|
+
# Note: Marianne font from French government requires manual download due to Cloudflare protection.
|
|
20
|
+
# Configure custom URL in pyproject.toml if you have a mirror or local copy.
|
|
19
21
|
DEFAULT_FONT_SOURCES = [
|
|
20
22
|
{
|
|
21
|
-
"name": "
|
|
22
|
-
"url": "https://
|
|
23
|
-
"description": "
|
|
23
|
+
"name": "Roboto",
|
|
24
|
+
"url": "https://github.com/googlefonts/roboto/releases/download/v2.138/roboto-unhinted.zip",
|
|
25
|
+
"description": "Google's sans-serif font family",
|
|
24
26
|
},
|
|
25
27
|
{
|
|
26
28
|
"name": "Roboto Mono",
|
|
27
|
-
"url": "https://
|
|
29
|
+
"url": "https://github.com/googlefonts/RobotoMono/archive/refs/heads/main.zip",
|
|
28
30
|
"description": "Google's monospace font, good for code",
|
|
29
31
|
},
|
|
30
32
|
]
|
|
31
33
|
|
|
34
|
+
# Additional font sources that may require special handling
|
|
35
|
+
OPTIONAL_FONT_SOURCES = {
|
|
36
|
+
"Marianne": {
|
|
37
|
+
"name": "Marianne",
|
|
38
|
+
"url": "https://www.info.gouv.fr/upload/media/content/0001/14/e9dd2398914853b3c21c402245866bc74cd3d3c5.zip",
|
|
39
|
+
"description": "French government official font - TTF version (Système de Design de l'État)",
|
|
40
|
+
"note": "May require manual download due to Cloudflare protection",
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
|
|
32
44
|
|
|
33
45
|
class FontInstaller:
|
|
34
46
|
"""Handles downloading and installing fonts for LaTeX projects"""
|
|
@@ -163,25 +175,30 @@ class FontInstaller:
|
|
|
163
175
|
url: URL to download
|
|
164
176
|
dest: Destination path
|
|
165
177
|
"""
|
|
166
|
-
#
|
|
167
|
-
headers = {
|
|
168
|
-
|
|
178
|
+
# Use requests library for better redirect and header handling
|
|
179
|
+
headers = {
|
|
180
|
+
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
181
|
+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
|
|
182
|
+
"Accept-Language": "en-US,en;q=0.5",
|
|
183
|
+
}
|
|
169
184
|
|
|
170
185
|
try:
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
186
|
+
response = requests.get(
|
|
187
|
+
url, headers=headers, allow_redirects=True, stream=True, timeout=60
|
|
188
|
+
)
|
|
189
|
+
response.raise_for_status()
|
|
190
|
+
|
|
191
|
+
# Get file size if available
|
|
192
|
+
content_length = response.headers.get("Content-Length")
|
|
193
|
+
total_size = int(content_length) if content_length else None
|
|
194
|
+
|
|
195
|
+
# Download in chunks
|
|
196
|
+
chunk_size = 8192
|
|
197
|
+
downloaded = 0
|
|
198
|
+
|
|
199
|
+
with open(dest, "wb") as f:
|
|
200
|
+
for chunk in response.iter_content(chunk_size=chunk_size):
|
|
201
|
+
if chunk:
|
|
185
202
|
f.write(chunk)
|
|
186
203
|
downloaded += len(chunk)
|
|
187
204
|
|
|
@@ -200,14 +217,16 @@ class FontInstaller:
|
|
|
200
217
|
flush=True,
|
|
201
218
|
)
|
|
202
219
|
|
|
203
|
-
|
|
220
|
+
print() # New line after progress
|
|
204
221
|
|
|
205
|
-
except HTTPError as e:
|
|
206
|
-
raise RuntimeError(f"HTTP error
|
|
207
|
-
except
|
|
208
|
-
raise RuntimeError(f"
|
|
209
|
-
except
|
|
222
|
+
except requests.exceptions.HTTPError as e:
|
|
223
|
+
raise RuntimeError(f"HTTP error: {e}")
|
|
224
|
+
except requests.exceptions.ConnectionError as e:
|
|
225
|
+
raise RuntimeError(f"Connection error: {e}")
|
|
226
|
+
except requests.exceptions.Timeout:
|
|
210
227
|
raise RuntimeError("Download timed out")
|
|
228
|
+
except requests.exceptions.RequestException as e:
|
|
229
|
+
raise RuntimeError(f"Download failed: {e}")
|
|
211
230
|
|
|
212
231
|
def _extract_zip(self, zip_path: Path, target_dir: Path) -> None:
|
|
213
232
|
"""
|
|
@@ -107,16 +107,15 @@ class TestFontInstaller:
|
|
|
107
107
|
assert len(files) == 1
|
|
108
108
|
assert files[0].name == "font1.ttf"
|
|
109
109
|
|
|
110
|
-
@patch("article_cli.fonts.
|
|
111
|
-
def test_download_file_success(self,
|
|
110
|
+
@patch("article_cli.fonts.requests.get")
|
|
111
|
+
def test_download_file_success(self, mock_get, tmp_path):
|
|
112
112
|
"""Test successful file download"""
|
|
113
113
|
# Create mock response
|
|
114
114
|
mock_response = MagicMock()
|
|
115
115
|
mock_response.headers.get.return_value = "1000"
|
|
116
|
-
mock_response.
|
|
117
|
-
mock_response.
|
|
118
|
-
|
|
119
|
-
mock_urlopen.return_value = mock_response
|
|
116
|
+
mock_response.iter_content.return_value = [b"test content"]
|
|
117
|
+
mock_response.raise_for_status = MagicMock()
|
|
118
|
+
mock_get.return_value = mock_response
|
|
120
119
|
|
|
121
120
|
installer = FontInstaller(fonts_dir=tmp_path / "fonts")
|
|
122
121
|
dest = tmp_path / "test.zip"
|
|
@@ -228,10 +227,10 @@ class TestDefaultFontSources:
|
|
|
228
227
|
assert "url" in source
|
|
229
228
|
assert source["url"].startswith("http")
|
|
230
229
|
|
|
231
|
-
def
|
|
232
|
-
"""Test that
|
|
230
|
+
def test_default_sources_include_roboto(self):
|
|
231
|
+
"""Test that Roboto font is included"""
|
|
233
232
|
names = [s["name"] for s in DEFAULT_FONT_SOURCES]
|
|
234
|
-
assert "
|
|
233
|
+
assert "Roboto" in names
|
|
235
234
|
|
|
236
235
|
def test_default_sources_include_roboto_mono(self):
|
|
237
236
|
"""Test that Roboto Mono font is included"""
|
|
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
|