article-cli 1.0.0__py3-none-any.whl
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/__init__.py +20 -0
- article_cli/__main__.py +8 -0
- article_cli/cli.py +308 -0
- article_cli/config.py +275 -0
- article_cli/git_manager.py +356 -0
- article_cli/zotero.py +359 -0
- article_cli-1.0.0.dist-info/METADATA +215 -0
- article_cli-1.0.0.dist-info/RECORD +11 -0
- article_cli-1.0.0.dist-info/WHEEL +4 -0
- article_cli-1.0.0.dist-info/entry_points.txt +2 -0
- article_cli-1.0.0.dist-info/licenses/LICENSE +21 -0
article_cli/__init__.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Article CLI - A command-line tool for managing LaTeX articles
|
|
3
|
+
|
|
4
|
+
This package provides tools for:
|
|
5
|
+
- Git release management with gitinfo2 support
|
|
6
|
+
- Zotero bibliography synchronization
|
|
7
|
+
- LaTeX build file cleanup
|
|
8
|
+
- Git hooks setup
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
__version__ = "1.0.0"
|
|
12
|
+
__author__ = "Christophe Prud'homme"
|
|
13
|
+
__email__ = "prudhomm@cemosis.fr"
|
|
14
|
+
|
|
15
|
+
from .cli import main
|
|
16
|
+
from .config import Config
|
|
17
|
+
from .zotero import ZoteroBibTexUpdater
|
|
18
|
+
from .git_manager import GitManager
|
|
19
|
+
|
|
20
|
+
__all__ = ["main", "Config", "ZoteroBibTexUpdater", "GitManager"]
|
article_cli/__main__.py
ADDED
article_cli/cli.py
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Main CLI interface for article-cli
|
|
3
|
+
|
|
4
|
+
Provides command-line interface for managing LaTeX articles with git and Zotero integration.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import argparse
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
from . import __version__
|
|
13
|
+
from .config import Config
|
|
14
|
+
from .zotero import ZoteroBibTexUpdater, print_error, print_info
|
|
15
|
+
from .git_manager import GitManager
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def create_parser() -> argparse.ArgumentParser:
|
|
19
|
+
"""Create and configure the argument parser"""
|
|
20
|
+
parser = argparse.ArgumentParser(
|
|
21
|
+
prog="article-cli",
|
|
22
|
+
description="CLI tool for managing LaTeX articles with git and Zotero integration",
|
|
23
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
24
|
+
epilog="""
|
|
25
|
+
Examples:
|
|
26
|
+
%(prog)s setup # Setup git hooks
|
|
27
|
+
%(prog)s clean # Clean build files
|
|
28
|
+
%(prog)s create v1.0.0 # Create release v1.0.0
|
|
29
|
+
%(prog)s list --count 10 # List 10 recent releases
|
|
30
|
+
%(prog)s delete v1.0.0 # Delete release
|
|
31
|
+
%(prog)s update-bibtex # Update from Zotero
|
|
32
|
+
%(prog)s config create # Create sample config file
|
|
33
|
+
|
|
34
|
+
Environment variables:
|
|
35
|
+
ZOTERO_API_KEY : Your Zotero API key (required for update-bibtex)
|
|
36
|
+
ZOTERO_USER_ID : Your Zotero user ID
|
|
37
|
+
ZOTERO_GROUP_ID : Your Zotero group ID
|
|
38
|
+
""",
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
parser.add_argument(
|
|
42
|
+
"--version", action="version", version=f"%(prog)s {__version__}"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
parser.add_argument("--config", type=Path, help="Path to configuration file")
|
|
46
|
+
|
|
47
|
+
subparsers = parser.add_subparsers(dest="command", help="Command to execute")
|
|
48
|
+
|
|
49
|
+
# Setup command
|
|
50
|
+
subparsers.add_parser("setup", help="Setup git hooks for gitinfo2")
|
|
51
|
+
|
|
52
|
+
# Clean command
|
|
53
|
+
subparsers.add_parser("clean", help="Clean LaTeX build files")
|
|
54
|
+
|
|
55
|
+
# Create command
|
|
56
|
+
create_parser = subparsers.add_parser("create", help="Create a new release")
|
|
57
|
+
create_parser.add_argument("version", help="Version tag (e.g., v1.0.0)")
|
|
58
|
+
create_parser.add_argument(
|
|
59
|
+
"--push", action="store_true", help="Automatically push the release"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# List command
|
|
63
|
+
list_parser = subparsers.add_parser("list", help="List releases")
|
|
64
|
+
list_parser.add_argument(
|
|
65
|
+
"--count", type=int, default=5, help="Number of releases to show"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# Delete command
|
|
69
|
+
delete_parser = subparsers.add_parser("delete", help="Delete a release")
|
|
70
|
+
delete_parser.add_argument("version", help="Version tag to delete")
|
|
71
|
+
delete_parser.add_argument(
|
|
72
|
+
"--remote", action="store_true", help="Also delete from remote"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Update-bibtex command
|
|
76
|
+
bibtex_parser = subparsers.add_parser(
|
|
77
|
+
"update-bibtex", help="Update BibTeX from Zotero"
|
|
78
|
+
)
|
|
79
|
+
bibtex_parser.add_argument("--api-key", help="Zotero API key")
|
|
80
|
+
bibtex_parser.add_argument("--user-id", help="Zotero user ID")
|
|
81
|
+
bibtex_parser.add_argument("--group-id", help="Zotero group ID")
|
|
82
|
+
bibtex_parser.add_argument(
|
|
83
|
+
"--output", default="references.bib", help="Output BibTeX file"
|
|
84
|
+
)
|
|
85
|
+
bibtex_parser.add_argument(
|
|
86
|
+
"--no-backup", action="store_true", help="Skip backup creation"
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# Config command
|
|
90
|
+
config_parser = subparsers.add_parser("config", help="Configuration management")
|
|
91
|
+
config_subparsers = config_parser.add_subparsers(
|
|
92
|
+
dest="config_command", help="Config subcommands"
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
config_create_parser = config_subparsers.add_parser(
|
|
96
|
+
"create", help="Create sample configuration file"
|
|
97
|
+
)
|
|
98
|
+
config_create_parser.add_argument("--path", type=Path, help="Path for config file")
|
|
99
|
+
|
|
100
|
+
config_subparsers.add_parser("show", help="Show current configuration")
|
|
101
|
+
|
|
102
|
+
return parser
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def handle_setup_command(config: Config) -> int:
|
|
106
|
+
"""Handle the setup command"""
|
|
107
|
+
try:
|
|
108
|
+
git_manager = GitManager()
|
|
109
|
+
return 0 if git_manager.setup_hooks() else 1
|
|
110
|
+
except ValueError as e:
|
|
111
|
+
print_error(str(e))
|
|
112
|
+
return 1
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def handle_clean_command(config: Config) -> int:
|
|
116
|
+
"""Handle the clean command"""
|
|
117
|
+
try:
|
|
118
|
+
git_manager = GitManager()
|
|
119
|
+
latex_config = config.get_latex_config()
|
|
120
|
+
return (
|
|
121
|
+
0 if git_manager.clean_latex_files(latex_config["clean_extensions"]) else 1
|
|
122
|
+
)
|
|
123
|
+
except ValueError as e:
|
|
124
|
+
print_error(str(e))
|
|
125
|
+
return 1
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def handle_create_command(args: argparse.Namespace, config: Config) -> int:
|
|
129
|
+
"""Handle the create command"""
|
|
130
|
+
try:
|
|
131
|
+
git_manager = GitManager()
|
|
132
|
+
git_config = config.get_git_config()
|
|
133
|
+
auto_push = args.push or git_config.get("auto_push", False)
|
|
134
|
+
return 0 if git_manager.create_release(args.version, auto_push) else 1
|
|
135
|
+
except ValueError as e:
|
|
136
|
+
print_error(str(e))
|
|
137
|
+
return 1
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def handle_list_command(args: argparse.Namespace, config: Config) -> int:
|
|
141
|
+
"""Handle the list command"""
|
|
142
|
+
try:
|
|
143
|
+
git_manager = GitManager()
|
|
144
|
+
return 0 if git_manager.list_releases(args.count) else 1
|
|
145
|
+
except ValueError as e:
|
|
146
|
+
print_error(str(e))
|
|
147
|
+
return 1
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def handle_delete_command(args: argparse.Namespace, config: Config) -> int:
|
|
151
|
+
"""Handle the delete command"""
|
|
152
|
+
try:
|
|
153
|
+
git_manager = GitManager()
|
|
154
|
+
return 0 if git_manager.delete_release(args.version, args.remote) else 1
|
|
155
|
+
except ValueError as e:
|
|
156
|
+
print_error(str(e))
|
|
157
|
+
return 1
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def handle_update_bibtex_command(args: argparse.Namespace, config: Config) -> int:
|
|
161
|
+
"""Handle the update-bibtex command"""
|
|
162
|
+
try:
|
|
163
|
+
# Get and validate Zotero configuration
|
|
164
|
+
zotero_config = config.validate_zotero_config(args)
|
|
165
|
+
|
|
166
|
+
updater = ZoteroBibTexUpdater(
|
|
167
|
+
api_key=zotero_config["api_key"],
|
|
168
|
+
user_id=zotero_config["user_id"],
|
|
169
|
+
group_id=zotero_config["group_id"],
|
|
170
|
+
output_file=zotero_config["output_file"],
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
return 0 if updater.update(backup=not args.no_backup) else 1
|
|
174
|
+
|
|
175
|
+
except ValueError as e:
|
|
176
|
+
print_error(str(e))
|
|
177
|
+
return 1
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def handle_config_command(args: argparse.Namespace, config: Config) -> int:
|
|
181
|
+
"""Handle config subcommands"""
|
|
182
|
+
if args.config_command == "create":
|
|
183
|
+
try:
|
|
184
|
+
path = args.path or Path.cwd() / ".article-cli.toml"
|
|
185
|
+
config.create_sample_config(path)
|
|
186
|
+
return 0
|
|
187
|
+
except Exception as e:
|
|
188
|
+
print_error(f"Failed to create config file: {e}")
|
|
189
|
+
return 1
|
|
190
|
+
|
|
191
|
+
elif args.config_command == "show":
|
|
192
|
+
try:
|
|
193
|
+
print_info("Current configuration:")
|
|
194
|
+
zotero_config = config.get_zotero_config()
|
|
195
|
+
git_config = config.get_git_config()
|
|
196
|
+
latex_config = config.get_latex_config()
|
|
197
|
+
|
|
198
|
+
print("\n[Zotero]")
|
|
199
|
+
print(f" API Key: {'***' if zotero_config['api_key'] else 'Not set'}")
|
|
200
|
+
print(f" User ID: {zotero_config['user_id'] or 'Not set'}")
|
|
201
|
+
|
|
202
|
+
# Show group ID with name if available
|
|
203
|
+
if zotero_config["group_id"]:
|
|
204
|
+
group_display = zotero_config["group_id"]
|
|
205
|
+
|
|
206
|
+
# Try to get group name if API key is available
|
|
207
|
+
if zotero_config["api_key"]:
|
|
208
|
+
try:
|
|
209
|
+
from .zotero import ZoteroBibTexUpdater
|
|
210
|
+
|
|
211
|
+
updater = ZoteroBibTexUpdater(
|
|
212
|
+
api_key=zotero_config["api_key"],
|
|
213
|
+
group_id=zotero_config["group_id"],
|
|
214
|
+
)
|
|
215
|
+
group_name = updater.get_group_name()
|
|
216
|
+
if group_name:
|
|
217
|
+
group_display = (
|
|
218
|
+
f"{zotero_config['group_id']} ({group_name})"
|
|
219
|
+
)
|
|
220
|
+
except Exception:
|
|
221
|
+
pass # Silently fall back to just showing ID
|
|
222
|
+
|
|
223
|
+
print(f" Group ID: {group_display}")
|
|
224
|
+
else:
|
|
225
|
+
print(f" Group ID: Not set")
|
|
226
|
+
|
|
227
|
+
print(f" Output File: {zotero_config['output_file']}")
|
|
228
|
+
|
|
229
|
+
print("\n[Git]")
|
|
230
|
+
print(f" Auto Push: {git_config['auto_push']}")
|
|
231
|
+
print(f" Default Branch: {git_config['default_branch']}")
|
|
232
|
+
|
|
233
|
+
print("\n[LaTeX]")
|
|
234
|
+
print(
|
|
235
|
+
f" Clean Extensions: {len(latex_config['clean_extensions'])} extensions"
|
|
236
|
+
)
|
|
237
|
+
print(f" Build Directory: {latex_config['build_dir']}")
|
|
238
|
+
|
|
239
|
+
return 0
|
|
240
|
+
except Exception as e:
|
|
241
|
+
print_error(f"Failed to show configuration: {e}")
|
|
242
|
+
return 1
|
|
243
|
+
else:
|
|
244
|
+
print_error("Unknown config command")
|
|
245
|
+
return 1
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def main(argv: Optional[list] = None) -> int:
|
|
249
|
+
"""
|
|
250
|
+
Main entry point for article-cli
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
argv: Command line arguments (defaults to sys.argv)
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
Exit code (0 for success, non-zero for error)
|
|
257
|
+
"""
|
|
258
|
+
parser = create_parser()
|
|
259
|
+
args = parser.parse_args(argv)
|
|
260
|
+
|
|
261
|
+
if not args.command:
|
|
262
|
+
parser.print_help()
|
|
263
|
+
return 1
|
|
264
|
+
|
|
265
|
+
# Initialize configuration
|
|
266
|
+
try:
|
|
267
|
+
config = Config(args.config)
|
|
268
|
+
except Exception as e:
|
|
269
|
+
print_error(f"Configuration error: {e}")
|
|
270
|
+
return 1
|
|
271
|
+
|
|
272
|
+
# Route to appropriate command handler
|
|
273
|
+
try:
|
|
274
|
+
if args.command == "setup":
|
|
275
|
+
return handle_setup_command(config)
|
|
276
|
+
|
|
277
|
+
elif args.command == "clean":
|
|
278
|
+
return handle_clean_command(config)
|
|
279
|
+
|
|
280
|
+
elif args.command == "create":
|
|
281
|
+
return handle_create_command(args, config)
|
|
282
|
+
|
|
283
|
+
elif args.command == "list":
|
|
284
|
+
return handle_list_command(args, config)
|
|
285
|
+
|
|
286
|
+
elif args.command == "delete":
|
|
287
|
+
return handle_delete_command(args, config)
|
|
288
|
+
|
|
289
|
+
elif args.command == "update-bibtex":
|
|
290
|
+
return handle_update_bibtex_command(args, config)
|
|
291
|
+
|
|
292
|
+
elif args.command == "config":
|
|
293
|
+
return handle_config_command(args, config)
|
|
294
|
+
|
|
295
|
+
else:
|
|
296
|
+
print_error(f"Unknown command: {args.command}")
|
|
297
|
+
return 1
|
|
298
|
+
|
|
299
|
+
except KeyboardInterrupt:
|
|
300
|
+
print("\n\nInterrupted by user")
|
|
301
|
+
return 130
|
|
302
|
+
except Exception as e:
|
|
303
|
+
print_error(f"Unexpected error: {e}")
|
|
304
|
+
return 1
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
if __name__ == "__main__":
|
|
308
|
+
sys.exit(main())
|
article_cli/config.py
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration management for article-cli
|
|
3
|
+
|
|
4
|
+
Supports both environment variables and TOML configuration files.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Dict, Any, Optional, Union
|
|
10
|
+
import argparse
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
import tomllib # Python 3.11+
|
|
14
|
+
except ImportError:
|
|
15
|
+
try:
|
|
16
|
+
import tomli as tomllib # fallback for older Python versions
|
|
17
|
+
except ImportError:
|
|
18
|
+
tomllib = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Config:
|
|
22
|
+
"""Configuration manager for article-cli"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, config_file: Optional[Union[str, Path]] = None):
|
|
25
|
+
"""
|
|
26
|
+
Initialize configuration manager
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
config_file: Optional path to configuration file
|
|
30
|
+
"""
|
|
31
|
+
self.config_file = config_file
|
|
32
|
+
self._config_data: Dict[str, Any] = {}
|
|
33
|
+
self._load_config()
|
|
34
|
+
|
|
35
|
+
def _find_config_file(self) -> Optional[Path]:
|
|
36
|
+
"""Find configuration file in project directory"""
|
|
37
|
+
if self.config_file:
|
|
38
|
+
config_path = Path(self.config_file)
|
|
39
|
+
if config_path.exists():
|
|
40
|
+
return config_path
|
|
41
|
+
else:
|
|
42
|
+
raise FileNotFoundError(f"Configuration file not found: {config_path}")
|
|
43
|
+
|
|
44
|
+
# Look for default config files (in priority order)
|
|
45
|
+
search_paths = [
|
|
46
|
+
Path.cwd()
|
|
47
|
+
/ ".article-cli.toml", # Dedicated config file (highest priority)
|
|
48
|
+
Path.cwd() / "pyproject.toml", # Project config file
|
|
49
|
+
Path.cwd() / "article-cli.toml", # Alternative dedicated file
|
|
50
|
+
Path.home() / ".config" / "article-cli" / "config.toml", # XDG config
|
|
51
|
+
Path.home() / ".article-cli.toml", # User config (lowest priority)
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
for path in search_paths:
|
|
55
|
+
if path.exists():
|
|
56
|
+
return path
|
|
57
|
+
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
def _load_config(self) -> None:
|
|
61
|
+
"""Load configuration from file if it exists"""
|
|
62
|
+
config_path = self._find_config_file()
|
|
63
|
+
|
|
64
|
+
if config_path and tomllib:
|
|
65
|
+
try:
|
|
66
|
+
with open(config_path, "rb") as f:
|
|
67
|
+
full_config = tomllib.load(f)
|
|
68
|
+
|
|
69
|
+
# Handle pyproject.toml vs dedicated config file
|
|
70
|
+
if config_path.name == "pyproject.toml":
|
|
71
|
+
# Extract article-cli config section from pyproject.toml
|
|
72
|
+
self._config_data = full_config.get("tool", {}).get(
|
|
73
|
+
"article-cli", {}
|
|
74
|
+
)
|
|
75
|
+
if self._config_data:
|
|
76
|
+
print(
|
|
77
|
+
f"Loaded configuration from: {config_path} [tool.article-cli]"
|
|
78
|
+
)
|
|
79
|
+
else:
|
|
80
|
+
# Fallback: look for legacy sections at root level
|
|
81
|
+
legacy_sections = ["zotero", "git", "latex"]
|
|
82
|
+
self._config_data = {
|
|
83
|
+
k: v for k, v in full_config.items() if k in legacy_sections
|
|
84
|
+
}
|
|
85
|
+
if self._config_data:
|
|
86
|
+
print(f"Loaded legacy configuration from: {config_path}")
|
|
87
|
+
else:
|
|
88
|
+
# Dedicated config file - use as-is
|
|
89
|
+
self._config_data = full_config
|
|
90
|
+
print(f"Loaded configuration from: {config_path}")
|
|
91
|
+
|
|
92
|
+
except Exception as e:
|
|
93
|
+
print(f"Warning: Could not load config file {config_path}: {e}")
|
|
94
|
+
self._config_data = {}
|
|
95
|
+
elif config_path and not tomllib:
|
|
96
|
+
print(
|
|
97
|
+
"Warning: TOML support not available. Install with: pip install tomli"
|
|
98
|
+
)
|
|
99
|
+
self._config_data = {}
|
|
100
|
+
else:
|
|
101
|
+
self._config_data = {}
|
|
102
|
+
|
|
103
|
+
def get(
|
|
104
|
+
self, section: str, key: str, default: Any = None, env_var: Optional[str] = None
|
|
105
|
+
) -> Any:
|
|
106
|
+
"""
|
|
107
|
+
Get configuration value with priority: CLI args > env vars > config file > default
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
section: Configuration section name
|
|
111
|
+
key: Configuration key name
|
|
112
|
+
default: Default value if not found
|
|
113
|
+
env_var: Environment variable name to check
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
Configuration value
|
|
117
|
+
"""
|
|
118
|
+
# Check environment variable first
|
|
119
|
+
if env_var and env_var in os.environ:
|
|
120
|
+
return os.environ[env_var]
|
|
121
|
+
|
|
122
|
+
# Check config file
|
|
123
|
+
if section in self._config_data and key in self._config_data[section]:
|
|
124
|
+
return self._config_data[section][key]
|
|
125
|
+
|
|
126
|
+
return default
|
|
127
|
+
|
|
128
|
+
def get_zotero_config(self) -> Dict[str, Optional[str]]:
|
|
129
|
+
"""Get Zotero-specific configuration"""
|
|
130
|
+
return {
|
|
131
|
+
"api_key": self.get("zotero", "api_key", env_var="ZOTERO_API_KEY"),
|
|
132
|
+
"user_id": self.get("zotero", "user_id", env_var="ZOTERO_USER_ID"),
|
|
133
|
+
"group_id": self.get("zotero", "group_id", env_var="ZOTERO_GROUP_ID"),
|
|
134
|
+
"output_file": self.get(
|
|
135
|
+
"zotero", "output_file", "references.bib", env_var="BIBTEX_FILE"
|
|
136
|
+
),
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
def get_git_config(self) -> Dict[str, Any]:
|
|
140
|
+
"""Get Git-specific configuration"""
|
|
141
|
+
return {
|
|
142
|
+
"auto_push": self.get("git", "auto_push", False),
|
|
143
|
+
"default_branch": self.get("git", "default_branch", "main"),
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
def get_latex_config(self) -> Dict[str, Any]:
|
|
147
|
+
"""Get LaTeX-specific configuration"""
|
|
148
|
+
default_extensions = [
|
|
149
|
+
".aux",
|
|
150
|
+
".bbl",
|
|
151
|
+
".blg",
|
|
152
|
+
".log",
|
|
153
|
+
".out",
|
|
154
|
+
".pyg",
|
|
155
|
+
".fls",
|
|
156
|
+
".synctex.gz",
|
|
157
|
+
".toc",
|
|
158
|
+
".fdb_latexmk",
|
|
159
|
+
".idx",
|
|
160
|
+
".ilg",
|
|
161
|
+
".ind",
|
|
162
|
+
".chl",
|
|
163
|
+
".lof",
|
|
164
|
+
".lot",
|
|
165
|
+
]
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
"clean_extensions": self.get(
|
|
169
|
+
"latex", "clean_extensions", default_extensions
|
|
170
|
+
),
|
|
171
|
+
"build_dir": self.get("latex", "build_dir", "."),
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
def validate_zotero_config(
|
|
175
|
+
self, args: argparse.Namespace
|
|
176
|
+
) -> Dict[str, Optional[str]]:
|
|
177
|
+
"""
|
|
178
|
+
Validate and merge Zotero configuration from args and config
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
args: Parsed command line arguments
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
Dict with validated Zotero configuration
|
|
185
|
+
|
|
186
|
+
Raises:
|
|
187
|
+
ValueError: If required configuration is missing
|
|
188
|
+
"""
|
|
189
|
+
config = self.get_zotero_config()
|
|
190
|
+
|
|
191
|
+
# Override with command line arguments
|
|
192
|
+
if hasattr(args, "api_key") and args.api_key:
|
|
193
|
+
config["api_key"] = args.api_key
|
|
194
|
+
if hasattr(args, "user_id") and args.user_id:
|
|
195
|
+
config["user_id"] = args.user_id
|
|
196
|
+
if hasattr(args, "group_id") and args.group_id:
|
|
197
|
+
config["group_id"] = args.group_id
|
|
198
|
+
if hasattr(args, "output") and args.output:
|
|
199
|
+
config["output_file"] = args.output
|
|
200
|
+
|
|
201
|
+
# Validate required fields
|
|
202
|
+
if not config["api_key"]:
|
|
203
|
+
raise ValueError(
|
|
204
|
+
"Zotero API key is required. Set via:\n"
|
|
205
|
+
" - Command line: --api-key YOUR_KEY\n"
|
|
206
|
+
" - Environment: export ZOTERO_API_KEY=YOUR_KEY\n"
|
|
207
|
+
' - Config file: [zotero] api_key = "YOUR_KEY"'
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
if not config["user_id"] and not config["group_id"]:
|
|
211
|
+
raise ValueError(
|
|
212
|
+
"Either Zotero user ID or group ID is required. Set via:\n"
|
|
213
|
+
" - Command line: --user-id ID or --group-id ID\n"
|
|
214
|
+
" - Environment: export ZOTERO_USER_ID=ID or ZOTERO_GROUP_ID=ID\n"
|
|
215
|
+
' - Config file: [zotero] user_id = "ID" or group_id = "ID"'
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
return config
|
|
219
|
+
|
|
220
|
+
def create_sample_config(self, path: Optional[Path] = None) -> Path:
|
|
221
|
+
"""
|
|
222
|
+
Create a sample configuration file
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
path: Optional path for config file
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
Path to created config file
|
|
229
|
+
"""
|
|
230
|
+
if path is None:
|
|
231
|
+
path = Path.cwd() / ".article-cli.toml"
|
|
232
|
+
|
|
233
|
+
sample_config = """# Article CLI Configuration File
|
|
234
|
+
# Copy this file to your project root as .article-cli.toml
|
|
235
|
+
|
|
236
|
+
[zotero]
|
|
237
|
+
# Your Zotero API key (get from https://www.zotero.org/settings/keys)
|
|
238
|
+
api_key = "your_api_key_here"
|
|
239
|
+
|
|
240
|
+
# Either user_id OR group_id (not both)
|
|
241
|
+
# user_id = "your_user_id"
|
|
242
|
+
group_id = "6219333"
|
|
243
|
+
|
|
244
|
+
# Output file for bibliography
|
|
245
|
+
output_file = "references.bib"
|
|
246
|
+
|
|
247
|
+
[git]
|
|
248
|
+
# Automatically push after creating releases
|
|
249
|
+
auto_push = true
|
|
250
|
+
|
|
251
|
+
# Default branch name
|
|
252
|
+
default_branch = "main"
|
|
253
|
+
|
|
254
|
+
[latex]
|
|
255
|
+
# File extensions to clean
|
|
256
|
+
clean_extensions = [
|
|
257
|
+
".aux", ".bbl", ".blg", ".log", ".out", ".pyg",
|
|
258
|
+
".fls", ".synctex.gz", ".toc", ".fdb_latexmk",
|
|
259
|
+
".idx", ".ilg", ".ind", ".chl", ".lof", ".lot"
|
|
260
|
+
]
|
|
261
|
+
|
|
262
|
+
# Build directory (relative to project root)
|
|
263
|
+
build_dir = "."
|
|
264
|
+
"""
|
|
265
|
+
|
|
266
|
+
try:
|
|
267
|
+
with open(path, "w") as f:
|
|
268
|
+
f.write(sample_config)
|
|
269
|
+
print(f"Created sample configuration file: {path}")
|
|
270
|
+
return path
|
|
271
|
+
except Exception as e:
|
|
272
|
+
raise RuntimeError(f"Could not create config file {path}: {e}")
|
|
273
|
+
|
|
274
|
+
def __repr__(self) -> str:
|
|
275
|
+
return f"Config(config_file={self.config_file}, sections={list(self._config_data.keys())})"
|