vyasa 0.3.6__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.
- vyasa/__init__.py +5 -0
- vyasa/agent.py +116 -0
- vyasa/build.py +660 -0
- vyasa/config.py +224 -0
- vyasa/core.py +2825 -0
- vyasa/helpers.py +349 -0
- vyasa/layout_helpers.py +40 -0
- vyasa/main.py +108 -0
- vyasa/static/scripts.js +1202 -0
- vyasa/static/sidenote.css +21 -0
- vyasa-0.3.6.dist-info/METADATA +227 -0
- vyasa-0.3.6.dist-info/RECORD +16 -0
- vyasa-0.3.6.dist-info/WHEEL +5 -0
- vyasa-0.3.6.dist-info/entry_points.txt +2 -0
- vyasa-0.3.6.dist-info/licenses/LICENSE +201 -0
- vyasa-0.3.6.dist-info/top_level.txt +1 -0
vyasa/config.py
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"""Configuration management for Vyasa.
|
|
2
|
+
|
|
3
|
+
Supports loading configuration from:
|
|
4
|
+
1. .vyasa file (TOML format) in the current directory or blog root
|
|
5
|
+
2. Environment variables (as fallback)
|
|
6
|
+
3. Default values
|
|
7
|
+
|
|
8
|
+
Priority: .vyasa file > environment variables > defaults
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import tomllib
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Optional
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class VyasaConfig:
|
|
18
|
+
|
|
19
|
+
"""Configuration handler for Vyasa."""
|
|
20
|
+
|
|
21
|
+
def __init__(self, config_path: Optional[Path] = None):
|
|
22
|
+
"""Initialize configuration.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
config_path: Optional path to .vyasa file. If not provided, will search
|
|
26
|
+
in current directory and VYASA_ROOT.
|
|
27
|
+
"""
|
|
28
|
+
self._config = {}
|
|
29
|
+
self._load_config(config_path)
|
|
30
|
+
|
|
31
|
+
def _load_config(self, config_path: Optional[Path] = None):
|
|
32
|
+
"""Load configuration from .vyasa file if it exists."""
|
|
33
|
+
# Try to find .vyasa file
|
|
34
|
+
config_file = None
|
|
35
|
+
|
|
36
|
+
if config_path and config_path.exists():
|
|
37
|
+
config_file = config_path
|
|
38
|
+
else:
|
|
39
|
+
# Search in VYASA_ROOT first (if set)
|
|
40
|
+
root = os.getenv('VYASA_ROOT')
|
|
41
|
+
if root:
|
|
42
|
+
root_config = Path(root) / '.vyasa'
|
|
43
|
+
if root_config.exists():
|
|
44
|
+
config_file = root_config
|
|
45
|
+
|
|
46
|
+
# Then search in current directory
|
|
47
|
+
if not config_file:
|
|
48
|
+
cwd_config = Path.cwd() / '.vyasa'
|
|
49
|
+
if cwd_config.exists():
|
|
50
|
+
config_file = cwd_config
|
|
51
|
+
|
|
52
|
+
# Load the config file if found
|
|
53
|
+
if config_file:
|
|
54
|
+
try:
|
|
55
|
+
with open(config_file, 'rb') as f:
|
|
56
|
+
self._config = tomllib.load(f)
|
|
57
|
+
print(f"✓ Loaded configuration from: {config_file}")
|
|
58
|
+
except Exception as e:
|
|
59
|
+
print(f"Warning: Failed to load {config_file}: {e}")
|
|
60
|
+
self._config = {}
|
|
61
|
+
|
|
62
|
+
def get(self, key: str, env_var: str, default: any = None) -> any:
|
|
63
|
+
"""Get configuration value with priority: config file > env var > default.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
key: Key in the .vyasa config file
|
|
67
|
+
env_var: Environment variable name
|
|
68
|
+
default: Default value if not found
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Configuration value
|
|
72
|
+
"""
|
|
73
|
+
# First check config file
|
|
74
|
+
if key in self._config:
|
|
75
|
+
return self._config[key]
|
|
76
|
+
|
|
77
|
+
# Then check environment variable
|
|
78
|
+
env_value = os.getenv(env_var)
|
|
79
|
+
if env_value is not None:
|
|
80
|
+
return env_value
|
|
81
|
+
|
|
82
|
+
# Finally return default
|
|
83
|
+
return default
|
|
84
|
+
|
|
85
|
+
def get_root_folder(self) -> Path:
|
|
86
|
+
"""Get the blog root folder path."""
|
|
87
|
+
root = self.get('root', 'VYASA_ROOT', '.')
|
|
88
|
+
return Path(root).resolve()
|
|
89
|
+
|
|
90
|
+
def get_blog_title(self) -> str:
|
|
91
|
+
"""Get the blog title."""
|
|
92
|
+
from .core import slug_to_title # Import here to avoid circular dependency
|
|
93
|
+
|
|
94
|
+
title = self.get('title', 'VYASA_TITLE', None)
|
|
95
|
+
if title:
|
|
96
|
+
return title.upper()
|
|
97
|
+
|
|
98
|
+
# Default to root folder name
|
|
99
|
+
return slug_to_title(self.get_root_folder().name).upper()
|
|
100
|
+
|
|
101
|
+
def get_host(self) -> str:
|
|
102
|
+
"""Get the server host."""
|
|
103
|
+
return self.get('host', 'VYASA_HOST', '127.0.0.1')
|
|
104
|
+
|
|
105
|
+
def get_port(self) -> int:
|
|
106
|
+
"""Get the server port."""
|
|
107
|
+
port = self.get('port', 'VYASA_PORT', 5001)
|
|
108
|
+
return int(port)
|
|
109
|
+
|
|
110
|
+
def get_auth(self):
|
|
111
|
+
"""Get authentication credentials from config, env, or default (None)."""
|
|
112
|
+
user = self.get('username', 'VYASA_USER', None)
|
|
113
|
+
pwd = self.get('password', 'VYASA_PASSWORD', None)
|
|
114
|
+
return user, pwd
|
|
115
|
+
|
|
116
|
+
def _coerce_list(self, value):
|
|
117
|
+
if value is None:
|
|
118
|
+
return []
|
|
119
|
+
if isinstance(value, list):
|
|
120
|
+
return [v for v in value if v is not None and str(v).strip()]
|
|
121
|
+
if isinstance(value, str):
|
|
122
|
+
parts = [v.strip() for v in value.split(",")]
|
|
123
|
+
return [v for v in parts if v]
|
|
124
|
+
return [value]
|
|
125
|
+
|
|
126
|
+
def get_auth_required(self):
|
|
127
|
+
"""Return auth_required if set, otherwise None."""
|
|
128
|
+
value = self.get('auth_required', 'VYASA_AUTH_REQUIRED', None)
|
|
129
|
+
if value is None:
|
|
130
|
+
return None
|
|
131
|
+
if isinstance(value, str):
|
|
132
|
+
return value.lower() in ('true', '1', 'yes', 'on')
|
|
133
|
+
return bool(value)
|
|
134
|
+
|
|
135
|
+
def get_google_oauth(self):
|
|
136
|
+
"""Get Google OAuth settings (optional)."""
|
|
137
|
+
cfg = self._config.get('google_oauth', {})
|
|
138
|
+
if not isinstance(cfg, dict):
|
|
139
|
+
cfg = {}
|
|
140
|
+
|
|
141
|
+
client_id = cfg.get('client_id') or self.get('google_client_id', 'VYASA_GOOGLE_CLIENT_ID', None)
|
|
142
|
+
client_secret = cfg.get('client_secret') or self.get('google_client_secret', 'VYASA_GOOGLE_CLIENT_SECRET', None)
|
|
143
|
+
allowed_domains = cfg.get('allowed_domains')
|
|
144
|
+
if allowed_domains is None:
|
|
145
|
+
allowed_domains = self.get('google_allowed_domains', 'VYASA_GOOGLE_ALLOWED_DOMAINS', [])
|
|
146
|
+
allowed_emails = cfg.get('allowed_emails')
|
|
147
|
+
if allowed_emails is None:
|
|
148
|
+
allowed_emails = self.get('google_allowed_emails', 'VYASA_GOOGLE_ALLOWED_EMAILS', [])
|
|
149
|
+
default_roles = cfg.get('default_roles')
|
|
150
|
+
if default_roles is None:
|
|
151
|
+
default_roles = self.get('google_default_roles', 'VYASA_GOOGLE_DEFAULT_ROLES', [])
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
"client_id": client_id,
|
|
155
|
+
"client_secret": client_secret,
|
|
156
|
+
"allowed_domains": self._coerce_list(allowed_domains),
|
|
157
|
+
"allowed_emails": self._coerce_list(allowed_emails),
|
|
158
|
+
"default_roles": self._coerce_list(default_roles),
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
def get_rbac(self):
|
|
162
|
+
"""Get RBAC settings (optional)."""
|
|
163
|
+
cfg = self._config.get('rbac', {})
|
|
164
|
+
if not isinstance(cfg, dict):
|
|
165
|
+
cfg = {}
|
|
166
|
+
|
|
167
|
+
enabled = cfg.get('enabled', None)
|
|
168
|
+
enabled_env = os.getenv('VYASA_RBAC_ENABLED')
|
|
169
|
+
if enabled_env is not None:
|
|
170
|
+
enabled = enabled_env.lower() in ('true', '1', 'yes', 'on')
|
|
171
|
+
if enabled is None:
|
|
172
|
+
enabled = bool(cfg.get('rules') or cfg.get('user_roles'))
|
|
173
|
+
|
|
174
|
+
default_roles = cfg.get('default_roles', None)
|
|
175
|
+
if default_roles is None:
|
|
176
|
+
default_roles = self.get('rbac_default_roles', 'VYASA_RBAC_DEFAULT_ROLES', [])
|
|
177
|
+
|
|
178
|
+
user_roles = cfg.get('user_roles', {})
|
|
179
|
+
if not isinstance(user_roles, dict):
|
|
180
|
+
user_roles = {}
|
|
181
|
+
|
|
182
|
+
role_users = cfg.get('role_users', {})
|
|
183
|
+
if not isinstance(role_users, dict):
|
|
184
|
+
role_users = {}
|
|
185
|
+
|
|
186
|
+
rules = cfg.get('rules', [])
|
|
187
|
+
if not isinstance(rules, list):
|
|
188
|
+
rules = []
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
"enabled": bool(enabled),
|
|
192
|
+
"default_roles": self._coerce_list(default_roles),
|
|
193
|
+
"user_roles": user_roles,
|
|
194
|
+
"role_users": role_users,
|
|
195
|
+
"rules": rules,
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
def get_sidebars_open(self) -> bool:
|
|
199
|
+
"""Get whether sidebars should be open by default."""
|
|
200
|
+
value = self.get('sidebars_open', 'VYASA_SIDEBARS_OPEN', True)
|
|
201
|
+
# Handle string values from environment variables
|
|
202
|
+
if isinstance(value, str):
|
|
203
|
+
return value.lower() in ('true', '1', 'yes', 'on')
|
|
204
|
+
return bool(value)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
# Global config instance
|
|
209
|
+
_config: Optional[VyasaConfig] = None
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def get_config() -> VyasaConfig:
|
|
213
|
+
"""Get or create the global configuration instance."""
|
|
214
|
+
global _config
|
|
215
|
+
if _config is None:
|
|
216
|
+
_config = VyasaConfig()
|
|
217
|
+
return _config
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def reload_config(config_path: Optional[Path] = None):
|
|
221
|
+
"""Reload configuration, optionally from a specific path."""
|
|
222
|
+
global _config
|
|
223
|
+
_config = VyasaConfig(config_path)
|
|
224
|
+
return _config
|