simple-resume 0.1.9__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.
Files changed (116) hide show
  1. simple_resume/__init__.py +132 -0
  2. simple_resume/core/__init__.py +47 -0
  3. simple_resume/core/colors.py +215 -0
  4. simple_resume/core/config.py +672 -0
  5. simple_resume/core/constants/__init__.py +207 -0
  6. simple_resume/core/constants/colors.py +98 -0
  7. simple_resume/core/constants/files.py +28 -0
  8. simple_resume/core/constants/layout.py +58 -0
  9. simple_resume/core/dependencies.py +258 -0
  10. simple_resume/core/effects.py +154 -0
  11. simple_resume/core/exceptions.py +261 -0
  12. simple_resume/core/file_operations.py +68 -0
  13. simple_resume/core/generate/__init__.py +21 -0
  14. simple_resume/core/generate/exceptions.py +69 -0
  15. simple_resume/core/generate/html.py +233 -0
  16. simple_resume/core/generate/pdf.py +659 -0
  17. simple_resume/core/generate/plan.py +131 -0
  18. simple_resume/core/hydration.py +55 -0
  19. simple_resume/core/importers/__init__.py +3 -0
  20. simple_resume/core/importers/json_resume.py +284 -0
  21. simple_resume/core/latex/__init__.py +60 -0
  22. simple_resume/core/latex/context.py +56 -0
  23. simple_resume/core/latex/conversion.py +227 -0
  24. simple_resume/core/latex/escaping.py +68 -0
  25. simple_resume/core/latex/fonts.py +93 -0
  26. simple_resume/core/latex/formatting.py +81 -0
  27. simple_resume/core/latex/sections.py +218 -0
  28. simple_resume/core/latex/types.py +84 -0
  29. simple_resume/core/markdown.py +127 -0
  30. simple_resume/core/models.py +102 -0
  31. simple_resume/core/palettes/__init__.py +38 -0
  32. simple_resume/core/palettes/common.py +73 -0
  33. simple_resume/core/palettes/data/default_palettes.json +58 -0
  34. simple_resume/core/palettes/exceptions.py +33 -0
  35. simple_resume/core/palettes/fetch_types.py +52 -0
  36. simple_resume/core/palettes/generators.py +137 -0
  37. simple_resume/core/palettes/registry.py +76 -0
  38. simple_resume/core/palettes/resolution.py +123 -0
  39. simple_resume/core/palettes/sources.py +162 -0
  40. simple_resume/core/paths.py +21 -0
  41. simple_resume/core/protocols.py +134 -0
  42. simple_resume/core/py.typed +0 -0
  43. simple_resume/core/render/__init__.py +37 -0
  44. simple_resume/core/render/manage.py +199 -0
  45. simple_resume/core/render/plan.py +405 -0
  46. simple_resume/core/result.py +226 -0
  47. simple_resume/core/resume.py +609 -0
  48. simple_resume/core/skills.py +60 -0
  49. simple_resume/core/validation.py +321 -0
  50. simple_resume/py.typed +0 -0
  51. simple_resume/shell/__init__.py +3 -0
  52. simple_resume/shell/assets/static/css/README.md +213 -0
  53. simple_resume/shell/assets/static/css/common.css +641 -0
  54. simple_resume/shell/assets/static/css/fonts.css +42 -0
  55. simple_resume/shell/assets/static/css/preview.css +82 -0
  56. simple_resume/shell/assets/static/css/print.css +99 -0
  57. simple_resume/shell/assets/static/fonts/AvenirLTStd-Book.otf +0 -0
  58. simple_resume/shell/assets/static/fonts/AvenirLTStd-Light.otf +0 -0
  59. simple_resume/shell/assets/static/fonts/AvenirLTStd-Medium.otf +0 -0
  60. simple_resume/shell/assets/static/fonts/AvenirLTStd-Oblique.otf +0 -0
  61. simple_resume/shell/assets/static/fonts/AvenirLTStd-Roman.otf +0 -0
  62. simple_resume/shell/assets/static/fonts/fontawesome/Font Awesome 6 Brands-Regular-400.otf +0 -0
  63. simple_resume/shell/assets/static/fonts/fontawesome/Font Awesome 6 Free-Solid-900.otf +0 -0
  64. simple_resume/shell/assets/static/images/default_profile_1.jpg +0 -0
  65. simple_resume/shell/assets/static/images/default_profile_2.png +0 -0
  66. simple_resume/shell/assets/static/schema.json +236 -0
  67. simple_resume/shell/assets/static/themes/README.md +208 -0
  68. simple_resume/shell/assets/static/themes/bold.yaml +64 -0
  69. simple_resume/shell/assets/static/themes/classic.yaml +64 -0
  70. simple_resume/shell/assets/static/themes/executive.yaml +64 -0
  71. simple_resume/shell/assets/static/themes/minimal.yaml +64 -0
  72. simple_resume/shell/assets/static/themes/modern.yaml +64 -0
  73. simple_resume/shell/assets/templates/html/cover.html +129 -0
  74. simple_resume/shell/assets/templates/html/demo.html +13 -0
  75. simple_resume/shell/assets/templates/html/resume_base.html +453 -0
  76. simple_resume/shell/assets/templates/html/resume_no_bars.html +316 -0
  77. simple_resume/shell/assets/templates/html/resume_with_bars.html +362 -0
  78. simple_resume/shell/cli/__init__.py +35 -0
  79. simple_resume/shell/cli/main.py +975 -0
  80. simple_resume/shell/cli/palette.py +75 -0
  81. simple_resume/shell/cli/random_palette_demo.py +407 -0
  82. simple_resume/shell/config.py +96 -0
  83. simple_resume/shell/effect_executor.py +211 -0
  84. simple_resume/shell/file_opener.py +308 -0
  85. simple_resume/shell/generate/__init__.py +37 -0
  86. simple_resume/shell/generate/core.py +650 -0
  87. simple_resume/shell/generate/lazy.py +284 -0
  88. simple_resume/shell/io_utils.py +199 -0
  89. simple_resume/shell/palettes/__init__.py +1 -0
  90. simple_resume/shell/palettes/fetch.py +63 -0
  91. simple_resume/shell/palettes/loader.py +321 -0
  92. simple_resume/shell/palettes/remote.py +179 -0
  93. simple_resume/shell/pdf_executor.py +52 -0
  94. simple_resume/shell/py.typed +0 -0
  95. simple_resume/shell/render/__init__.py +1 -0
  96. simple_resume/shell/render/latex.py +308 -0
  97. simple_resume/shell/render/operations.py +240 -0
  98. simple_resume/shell/resume_extensions.py +737 -0
  99. simple_resume/shell/runtime/__init__.py +7 -0
  100. simple_resume/shell/runtime/content.py +190 -0
  101. simple_resume/shell/runtime/generate.py +497 -0
  102. simple_resume/shell/runtime/lazy.py +138 -0
  103. simple_resume/shell/runtime/lazy_import.py +173 -0
  104. simple_resume/shell/service_locator.py +80 -0
  105. simple_resume/shell/services.py +256 -0
  106. simple_resume/shell/session/__init__.py +6 -0
  107. simple_resume/shell/session/config.py +35 -0
  108. simple_resume/shell/session/manage.py +386 -0
  109. simple_resume/shell/strategies.py +181 -0
  110. simple_resume/shell/themes/__init__.py +35 -0
  111. simple_resume/shell/themes/loader.py +230 -0
  112. simple_resume-0.1.9.dist-info/METADATA +201 -0
  113. simple_resume-0.1.9.dist-info/RECORD +116 -0
  114. simple_resume-0.1.9.dist-info/WHEEL +4 -0
  115. simple_resume-0.1.9.dist-info/entry_points.txt +5 -0
  116. simple_resume-0.1.9.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,138 @@
1
+ """Lazy-loaded generation functions for optimal import performance.
2
+
3
+ This module provides thin wrappers around the core generation functions
4
+ with lazy loading to reduce startup memory footprint.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import importlib
10
+ from functools import lru_cache
11
+ from pathlib import Path
12
+ from types import ModuleType
13
+ from typing import Any
14
+
15
+ from simple_resume.core.models import GenerationConfig
16
+
17
+
18
+ class _LazyRuntimeLoader:
19
+ """Lazy loader for runtime generation functions."""
20
+
21
+ def __init__(self) -> None:
22
+ self._core: ModuleType | None = None
23
+ self._loaded = False
24
+
25
+ def _load_core(self) -> ModuleType:
26
+ """Load runtime module if not already loaded."""
27
+ if not self._loaded:
28
+ self._core = importlib.import_module(".generate", package=__package__)
29
+ self._loaded = True
30
+ # _core is set when _loaded is True
31
+ return self._core # type: ignore[return-value]
32
+
33
+ @property
34
+ def generate_pdf(self) -> Any:
35
+ """Get generate_pdf function from core module."""
36
+ return self._load_core().generate_pdf
37
+
38
+ @property
39
+ def generate_html(self) -> Any:
40
+ """Get generate_html function from core module."""
41
+ return self._load_core().generate_html
42
+
43
+ @property
44
+ def generate_all(self) -> Any:
45
+ """Get generate_all function from core module."""
46
+ return self._load_core().generate_all
47
+
48
+ @property
49
+ def generate_resume(self) -> Any:
50
+ """Get generate_resume function from core module."""
51
+ return self._load_core().generate_resume
52
+
53
+ @property
54
+ def generate(self) -> Any:
55
+ """Get generate function from core module."""
56
+ return self._load_core().generate
57
+
58
+ @property
59
+ def preview(self) -> Any:
60
+ """Get preview function from core module."""
61
+ return self._load_core().preview
62
+
63
+
64
+ @lru_cache(maxsize=1)
65
+ def _get_lazy_core() -> _LazyRuntimeLoader:
66
+ """Return a shared lazy loader instance without module-level globals."""
67
+ return _LazyRuntimeLoader()
68
+
69
+
70
+ def generate_pdf(
71
+ config: GenerationConfig,
72
+ **config_overrides: Any,
73
+ ) -> Any:
74
+ """Generate PDF resumes using a configuration object (lazy-loaded wrapper)."""
75
+ return _get_lazy_core().generate_pdf(config, **config_overrides)
76
+
77
+
78
+ def generate_html(
79
+ config: GenerationConfig,
80
+ **config_overrides: Any,
81
+ ) -> Any:
82
+ """Generate HTML resumes using a configuration object (lazy-loaded wrapper)."""
83
+ return _get_lazy_core().generate_html(config, **config_overrides)
84
+
85
+
86
+ def generate_all(
87
+ config: GenerationConfig,
88
+ **config_overrides: Any,
89
+ ) -> Any:
90
+ """Generate resumes in all specified formats (lazy-loaded wrapper)."""
91
+ return _get_lazy_core().generate_all(config, **config_overrides)
92
+
93
+
94
+ def generate_resume(
95
+ config: GenerationConfig,
96
+ **config_overrides: Any,
97
+ ) -> Any:
98
+ """Generate a resume in a specific format (lazy-loaded wrapper)."""
99
+ return _get_lazy_core().generate_resume(config, **config_overrides)
100
+
101
+
102
+ def generate(
103
+ source: str | Path,
104
+ options: Any | None = None,
105
+ **overrides: Any,
106
+ ) -> Any:
107
+ """Render one or more formats for the same source (lazy-loaded wrapper)."""
108
+ return _get_lazy_core().generate(source, options, **overrides)
109
+
110
+
111
+ def preview(
112
+ source: str | Path,
113
+ *,
114
+ data_dir: str | Path | None = None,
115
+ template: str | None = None,
116
+ browser: str | None = None,
117
+ open_after: bool = True,
118
+ **overrides: Any,
119
+ ) -> Any:
120
+ """Render a single resume to HTML with preview defaults (lazy-loaded wrapper)."""
121
+ return _get_lazy_core().preview(
122
+ source,
123
+ data_dir=data_dir,
124
+ template=template,
125
+ browser=browser,
126
+ open_after=open_after,
127
+ **overrides,
128
+ )
129
+
130
+
131
+ __all__ = [
132
+ "generate_pdf",
133
+ "generate_html",
134
+ "generate_all",
135
+ "generate_resume",
136
+ "generate",
137
+ "preview",
138
+ ]
@@ -0,0 +1,173 @@
1
+ """Lazy loading utilities for optimizing import performance.
2
+
3
+ This module provides utilities to defer heavy imports until actually needed,
4
+ reducing initial memory footprint and improving startup performance.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import importlib
10
+ from functools import lru_cache
11
+ from typing import Any, Callable, TypeVar, cast
12
+
13
+ T = TypeVar("T")
14
+
15
+
16
+ class LazyModule:
17
+ """A module that loads on first attribute access."""
18
+
19
+ def __init__(self, module_name: str) -> None:
20
+ """Initialize lazy module.
21
+
22
+ Args:
23
+ module_name: Full module path to import when needed
24
+
25
+ """
26
+ self._module_name = module_name
27
+ self._module: Any = None
28
+ self._loaded = False
29
+
30
+ def _load(self) -> Any:
31
+ """Load the module if not already loaded."""
32
+ if not self._loaded:
33
+ self._module = importlib.import_module(self._module_name)
34
+ self._loaded = True
35
+ return self._module
36
+
37
+ def __getattr__(self, name: str) -> Any:
38
+ """Get attribute from the lazily loaded module."""
39
+ module = self._load()
40
+ return getattr(module, name)
41
+
42
+ def __dir__(self) -> list[str]:
43
+ """Return directory listing of the module when loaded."""
44
+ module = self._load()
45
+ return dir(module)
46
+
47
+
48
+ class LazyFunction:
49
+ """A function that loads its implementation on first call."""
50
+
51
+ def __init__(self, module_path: str, function_name: str) -> None:
52
+ """Initialize lazy function.
53
+
54
+ Args:
55
+ module_path: Full module path containing the function
56
+ function_name: Name of the function to lazy-load
57
+
58
+ """
59
+ self._module_path = module_path
60
+ self._function_name = function_name
61
+ self._function: Callable[..., Any] | None = None
62
+ self._loaded = False
63
+
64
+ def _load(self) -> Callable[..., Any]:
65
+ """Load the function if not already loaded."""
66
+ if not self._loaded:
67
+ module = importlib.import_module(self._module_path)
68
+ self._function = getattr(module, self._function_name)
69
+ self._loaded = True
70
+ # After loading, _function is guaranteed to be set
71
+ return cast(Callable[..., Any], self._function)
72
+
73
+ def __call__(self, *args: Any, **kwargs: Any) -> Any:
74
+ """Call the lazily loaded function."""
75
+ function = self._load()
76
+ return function(*args, **kwargs)
77
+
78
+
79
+ @lru_cache(maxsize=128)
80
+ def lazy_import(module_path: str) -> LazyModule:
81
+ """Create a cached lazy module instance.
82
+
83
+ Args:
84
+ module_path: Full module path to import lazily
85
+
86
+ Returns:
87
+ LazyModule instance that loads on first access
88
+
89
+ """
90
+ return LazyModule(module_path)
91
+
92
+
93
+ @lru_cache(maxsize=64)
94
+ def lazy_function(module_path: str, function_name: str) -> LazyFunction:
95
+ """Create a cached lazy function instance.
96
+
97
+ Args:
98
+ module_path: Full module path containing the function
99
+ function_name: Name of the function to lazy-load
100
+
101
+ Returns:
102
+ LazyFunction instance that loads on first call
103
+
104
+ """
105
+ return LazyFunction(module_path, function_name)
106
+
107
+
108
+ # Common lazy-loaded modules
109
+ _lazy_session = lazy_import("simple_resume.shell.session")
110
+ _lazy_result = lazy_import("simple_resume.core.result")
111
+ _lazy_validation = lazy_import("simple_resume.core.validation")
112
+
113
+
114
+ # Public lazy API functions
115
+ def lazy_create_session(*args: Any, **kwargs: Any) -> Any:
116
+ """Lazily loaded create_session function."""
117
+ return _lazy_session.create_session(*args, **kwargs)
118
+
119
+
120
+ def lazy_ResumeSession(*args: Any, **kwargs: Any) -> Any:
121
+ """Lazily loaded ResumeSession class."""
122
+ return _lazy_session.ResumeSession(*args, **kwargs)
123
+
124
+
125
+ def lazy_SessionConfig(*args: Any, **kwargs: Any) -> Any:
126
+ """Lazily loaded SessionConfig class."""
127
+ return _lazy_session.SessionConfig(*args, **kwargs)
128
+
129
+
130
+ def lazy_GenerationResult(*args: Any, **kwargs: Any) -> Any:
131
+ """Lazily loaded GenerationResult class."""
132
+ return _lazy_result.GenerationResult(*args, **kwargs)
133
+
134
+
135
+ def lazy_BatchGenerationResult(*args: Any, **kwargs: Any) -> Any:
136
+ """Lazily loaded BatchGenerationResult class."""
137
+ return _lazy_result.BatchGenerationResult(*args, **kwargs)
138
+
139
+
140
+ def lazy_GenerationMetadata(*args: Any, **kwargs: Any) -> Any:
141
+ """Lazily loaded GenerationMetadata class."""
142
+ return _lazy_result.GenerationMetadata(*args, **kwargs)
143
+
144
+
145
+ def lazy_validate_directory_path(*args: Any, **kwargs: Any) -> Any:
146
+ """Lazily loaded validate_directory_path function."""
147
+ return _lazy_validation.validate_directory_path(*args, **kwargs)
148
+
149
+
150
+ def lazy_validate_format(*args: Any, **kwargs: Any) -> Any:
151
+ """Lazily loaded validate_format function."""
152
+ return _lazy_validation.validate_format(*args, **kwargs)
153
+
154
+
155
+ def lazy_validate_template_name(*args: Any, **kwargs: Any) -> Any:
156
+ """Lazily loaded validate_template_name function."""
157
+ return _lazy_validation.validate_template_name(*args, **kwargs)
158
+
159
+
160
+ __all__ = [
161
+ "LazyModule",
162
+ "LazyFunction",
163
+ "lazy_import",
164
+ "lazy_function",
165
+ "lazy_create_session",
166
+ "lazy_ResumeSession",
167
+ "lazy_SessionConfig",
168
+ "lazy_GenerationResult",
169
+ "lazy_BatchGenerationResult",
170
+ "lazy_validate_directory_path",
171
+ "lazy_validate_format",
172
+ "lazy_validate_template_name",
173
+ ]
@@ -0,0 +1,80 @@
1
+ """Service locator pattern for dependency injection.
2
+
3
+ This module provides a clean way to inject shell-layer dependencies
4
+ into core functions without using late-bound imports.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from functools import lru_cache
10
+ from typing import Any, Callable, TypeVar
11
+
12
+ T = TypeVar("T")
13
+
14
+
15
+ class ServiceLocator:
16
+ """Simple service locator for dependency injection."""
17
+
18
+ def __init__(self) -> None:
19
+ """Initialize the service locator."""
20
+ self._services: dict[str, Any] = {}
21
+ self._factories: dict[str, Callable[[], Any]] = {}
22
+
23
+ def register(self, name: str, service: Any) -> None:
24
+ """Register a service instance."""
25
+ self._services[name] = service
26
+
27
+ def register_factory(self, name: str, factory: Callable[[], Any]) -> None:
28
+ """Register a factory function for lazy service creation."""
29
+ self._factories[name] = factory
30
+
31
+ def get(self, name: str, service_type: type[T]) -> T:
32
+ """Get a service by name, ensuring it matches the expected type."""
33
+ if name in self._services:
34
+ service = self._services[name]
35
+ if not isinstance(service, service_type):
36
+ raise TypeError(f"Service {name} is not of type {service_type}")
37
+ return service
38
+
39
+ if name in self._factories:
40
+ service = self._factories[name]()
41
+ if not isinstance(service, service_type):
42
+ raise TypeError(f"Service {name} is not of type {service_type}")
43
+ self._services[name] = service # Cache the instance
44
+ return service
45
+
46
+ raise ValueError(f"Service {name} not registered")
47
+
48
+ def has(self, name: str) -> bool:
49
+ """Check if a service is registered."""
50
+ return name in self._services or name in self._factories
51
+
52
+
53
+ @lru_cache(maxsize=1)
54
+ def get_service_locator() -> ServiceLocator:
55
+ """Get the global service locator instance without module globals."""
56
+ return ServiceLocator()
57
+
58
+
59
+ def register_service(name: str, service: Any) -> None:
60
+ """Register a service with the global locator."""
61
+ get_service_locator().register(name, service)
62
+
63
+
64
+ def register_service_factory(name: str, factory: Callable[[], Any]) -> None:
65
+ """Register a service factory with the global locator."""
66
+ get_service_locator().register_factory(name, factory)
67
+
68
+
69
+ def get_service(name: str, service_type: type[T]) -> T:
70
+ """Get a service from the global locator."""
71
+ return get_service_locator().get(name, service_type)
72
+
73
+
74
+ __all__ = [
75
+ "ServiceLocator",
76
+ "get_service_locator",
77
+ "register_service",
78
+ "register_service_factory",
79
+ "get_service",
80
+ ]
@@ -0,0 +1,256 @@
1
+ """Shell layer implementations of core protocols.
2
+
3
+ This module provides concrete implementations of the protocols defined
4
+ in the core layer, enabling dependency injection without late-bound imports.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import copy
10
+ from pathlib import Path
11
+ from typing import TYPE_CHECKING, Any
12
+
13
+ if TYPE_CHECKING:
14
+ pass
15
+
16
+ from simple_resume.core.generate.html import create_html_generator_factory
17
+ from simple_resume.core.generate.pdf import PdfGeneratorFactory
18
+ from simple_resume.core.markdown import render_markdown_content
19
+ from simple_resume.core.protocols import (
20
+ ContentLoader,
21
+ EffectExecutor,
22
+ FileOpenerService,
23
+ HtmlGenerator,
24
+ LaTeXRenderer,
25
+ PaletteLoader,
26
+ PathResolver,
27
+ PdfGenerationStrategy,
28
+ TemplateLocator,
29
+ )
30
+ from simple_resume.core.resume import set_default_loaders
31
+ from simple_resume.shell.config import TEMPLATE_LOC
32
+ from simple_resume.shell.effect_executor import EffectExecutor as ShellEffectExecutor
33
+ from simple_resume.shell.file_opener import open_file as shell_open_file
34
+ from simple_resume.shell.io_utils import candidate_yaml_path, resolve_paths_for_read
35
+ from simple_resume.shell.palettes.loader import get_palette_registry
36
+ from simple_resume.shell.render.latex import (
37
+ LatexCompilationError,
38
+ compile_tex_to_pdf,
39
+ render_resume_latex_from_data,
40
+ )
41
+ from simple_resume.shell.render.operations import (
42
+ generate_html_with_jinja as shell_generate,
43
+ )
44
+ from simple_resume.shell.runtime.content import get_content, load_palette_from_file
45
+ from simple_resume.shell.service_locator import register_service
46
+ from simple_resume.shell.strategies import (
47
+ LatexStrategy,
48
+ PdfGenerationRequest,
49
+ WeasyPrintStrategy,
50
+ )
51
+
52
+
53
+ class DefaultTemplateLocator(TemplateLocator):
54
+ """Default template locator implementation."""
55
+
56
+ def get_template_location(self) -> Path:
57
+ """Get the template directory path."""
58
+ return TEMPLATE_LOC
59
+
60
+
61
+ class DefaultEffectExecutor(EffectExecutor):
62
+ """Default effect executor implementation."""
63
+
64
+ def __init__(self) -> None:
65
+ """Initialize the default effect executor."""
66
+ self._executor = ShellEffectExecutor()
67
+
68
+ def execute(self, effect: Any) -> None:
69
+ """Execute a single effect."""
70
+ self._executor.execute(effect)
71
+
72
+ def execute_many(self, effects: list[Any]) -> None:
73
+ """Execute multiple effects."""
74
+ self._executor.execute_many(effects)
75
+
76
+
77
+ class DefaultPathResolver(PathResolver):
78
+ """Default path resolver implementation."""
79
+
80
+ def candidate_yaml_path(self, name: str) -> Path:
81
+ """Get candidate YAML path for a name."""
82
+ result = candidate_yaml_path(name)
83
+ if result is None:
84
+ # Fallback to creating a Path from name
85
+ return Path(name)
86
+ return result
87
+
88
+ def resolve_paths_for_read(
89
+ self,
90
+ paths: Any,
91
+ overrides: dict[str, Any],
92
+ candidate_path: Path,
93
+ ) -> Any:
94
+ """Resolve paths for reading operations."""
95
+ return resolve_paths_for_read(paths, overrides, candidate_path)
96
+
97
+
98
+ class DefaultContentLoader(ContentLoader):
99
+ """Default content loader implementation."""
100
+
101
+ def __init__(self) -> None:
102
+ """Initialize the default content loader."""
103
+ self._path_resolver = DefaultPathResolver()
104
+
105
+ def load(
106
+ self,
107
+ name: str,
108
+ paths: Any,
109
+ transform_markdown: bool,
110
+ ) -> tuple[dict[str, Any], dict[str, Any]]:
111
+ """Load content from a YAML file."""
112
+ candidate_path = self._path_resolver.candidate_yaml_path(name)
113
+ resolved_paths = self._path_resolver.resolve_paths_for_read(
114
+ paths, {}, candidate_path
115
+ )
116
+ raw_data = get_content(name, paths=resolved_paths, transform_markdown=False)
117
+
118
+ if transform_markdown:
119
+ processed_data = render_markdown_content(raw_data)
120
+ else:
121
+ processed_data = copy.deepcopy(raw_data)
122
+ return processed_data, raw_data
123
+
124
+
125
+ class DefaultPdfGenerationStrategy(PdfGenerationStrategy):
126
+ """Default PDF generation strategy implementation."""
127
+
128
+ def __init__(self, mode: str = "weasyprint") -> None:
129
+ """Initialize the PDF generation strategy.
130
+
131
+ Args:
132
+ mode: PDF generation mode ('latex' or 'weasyprint')
133
+
134
+ """
135
+ if mode == "latex":
136
+ self._strategy: LatexStrategy | WeasyPrintStrategy = LatexStrategy()
137
+ else:
138
+ self._strategy = WeasyPrintStrategy()
139
+
140
+ def generate(
141
+ self,
142
+ render_plan: Any,
143
+ output_path: Path,
144
+ resume_name: str,
145
+ filename: str | None = None,
146
+ ) -> tuple[Any, int | None]:
147
+ """Generate a PDF file."""
148
+ if not isinstance(render_plan, PdfGenerationRequest):
149
+ raise TypeError(
150
+ "render_plan must be a PdfGenerationRequest; "
151
+ "legacy inputs are not supported"
152
+ )
153
+ request = render_plan
154
+ result = self._strategy.generate_pdf(request)
155
+ return result, None if hasattr(result, "page_count") else None
156
+
157
+
158
+ class DefaultHtmlGenerator(HtmlGenerator):
159
+ """Default HTML generator implementation."""
160
+
161
+ def generate(
162
+ self,
163
+ render_plan: Any,
164
+ output_path: Path,
165
+ filename: str | None = None,
166
+ ) -> Any:
167
+ """Generate HTML content."""
168
+ # Use shell's render operations directly since this service is in shell layer
169
+ return shell_generate(render_plan, output_path, filename)
170
+
171
+
172
+ class DefaultFileOpenerService(FileOpenerService):
173
+ """Default file opener service implementation."""
174
+
175
+ def open_file(self, path: Path, format_type: str | None = None) -> bool:
176
+ """Open a file with the system default application."""
177
+ return shell_open_file(path, format_type)
178
+
179
+
180
+ class DefaultPaletteLoader(PaletteLoader):
181
+ """Default palette loader implementation."""
182
+
183
+ def load_palette_from_file(self, path: str | Path) -> dict[str, Any]:
184
+ """Load a palette from a file."""
185
+ return load_palette_from_file(path)
186
+
187
+
188
+ class DefaultLaTeXRenderer(LaTeXRenderer):
189
+ """Default LaTeX renderer implementation."""
190
+
191
+ def get_latex_functions(self) -> tuple[Any, Any, Any]:
192
+ """Get LaTeX compilation functions."""
193
+ try:
194
+ return (
195
+ LatexCompilationError,
196
+ compile_tex_to_pdf,
197
+ render_resume_latex_from_data,
198
+ )
199
+ except ImportError:
200
+ return None, None, None
201
+
202
+
203
+ def register_default_services() -> None:
204
+ """Register all default services with the service locator."""
205
+ # Create default implementations
206
+ content_loader = DefaultContentLoader()
207
+ path_resolver = DefaultPathResolver()
208
+ palette_loader = DefaultPaletteLoader()
209
+ template_locator = DefaultTemplateLocator()
210
+ effect_executor = DefaultEffectExecutor()
211
+ latex_renderer = DefaultLaTeXRenderer()
212
+
213
+ # Set default dependencies for core HTML generation
214
+ html_factory = create_html_generator_factory(template_locator)
215
+
216
+ # Set default dependencies for core PDF generation
217
+ pdf_factory = PdfGeneratorFactory(
218
+ effect_executor=effect_executor,
219
+ template_locator=template_locator,
220
+ latex_renderer=latex_renderer,
221
+ )
222
+
223
+ # Register with service locator (for legacy compatibility)
224
+ register_service("html_generator_factory", html_factory)
225
+ register_service("pdf_generator_factory", pdf_factory)
226
+ register_service("file_opener", DefaultFileOpenerService())
227
+ register_service("palette_loader", palette_loader)
228
+ register_service("latex_renderer", latex_renderer)
229
+ register_service("html_generator", DefaultHtmlGenerator())
230
+ register_service("pdf_generation_strategy", DefaultPdfGenerationStrategy())
231
+
232
+ # Set default loaders for core Resume class
233
+ set_default_loaders(
234
+ content_loader=content_loader,
235
+ palette_loader=palette_loader,
236
+ path_resolver=path_resolver,
237
+ palette_registry_provider=get_palette_registry,
238
+ )
239
+
240
+ # Warm the palette registry once at startup to avoid expensive discovery
241
+ # during latency-sensitive operations (e.g., concurrent renders in tests).
242
+ get_palette_registry()
243
+
244
+
245
+ __all__ = [
246
+ "DefaultTemplateLocator",
247
+ "DefaultEffectExecutor",
248
+ "DefaultPathResolver",
249
+ "DefaultContentLoader",
250
+ "DefaultPdfGenerationStrategy",
251
+ "DefaultHtmlGenerator",
252
+ "DefaultFileOpenerService",
253
+ "DefaultPaletteLoader",
254
+ "DefaultLaTeXRenderer",
255
+ "register_default_services",
256
+ ]
@@ -0,0 +1,6 @@
1
+ """Session management for simple-resume operations."""
2
+
3
+ from simple_resume.shell.session.config import SessionConfig
4
+ from simple_resume.shell.session.manage import ResumeSession, create_session
5
+
6
+ __all__ = ["SessionConfig", "ResumeSession", "create_session"]
@@ -0,0 +1,35 @@
1
+ """Session configuration for simple-resume operations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ from simple_resume.core.constants import OutputFormat
10
+ from simple_resume.core.exceptions import ConfigurationError
11
+ from simple_resume.core.paths import Paths
12
+
13
+
14
+ @dataclass
15
+ class SessionConfig:
16
+ """Configuration for a `ResumeSession`."""
17
+
18
+ paths: Paths | None = None
19
+ default_template: str | None = None
20
+ default_palette: str | None = None
21
+ default_format: OutputFormat | str = OutputFormat.PDF
22
+ auto_open: bool = False
23
+ preview_mode: bool = False
24
+ output_dir: Path | None = None
25
+ # Additional session-wide settings
26
+ session_metadata: dict[str, Any] = field(default_factory=dict)
27
+
28
+ def __post_init__(self) -> None:
29
+ """Normalize enum-backed fields."""
30
+ try:
31
+ self.default_format = OutputFormat.normalize(self.default_format)
32
+ except (ValueError, TypeError) as exc:
33
+ raise ConfigurationError(
34
+ f"Invalid default format: {self.default_format}"
35
+ ) from exc