zer0-image-generator 0.2.1 → 0.3.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +21 -0
- data/README.md +24 -2
- data/lib/zer0_image_generator/command.rb +3 -1
- data/lib/zer0_image_generator/preview_generator.py +73 -26
- data/lib/zer0_image_generator/version.rb +1 -1
- metadata +3 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 24975e688896196e8e883c15ed17d09e5856a9b68f18b47b347bc61d701cc265
|
|
4
|
+
data.tar.gz: da2d123ed3067f36c2e5962bd3a351a9d1ae48e1fd97bfe6dbcccb23aac53e52
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2add913663a1f13bfebc00746b3ace4ebf5c4e096fd34ee1db89754245762d011e1a2ca8173ed2f0aefbf46abdacb041625f9ac916f9b8e48aa13c6c38c87467
|
|
7
|
+
data.tar.gz: e275f913cfeb197728138bc154a6963e31b1cdf0a1b11081a8526ef002667dfa504b1c7a0e55bac799a5977a599995012f2fe0f66aa28a3e5b28d599244c16a1
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,27 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.3.0] - 2026-07-14
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Per-collection style/theme overrides: a `collection_styles:` map under
|
|
13
|
+
`preview_images:` lets each collection (`posts`, `docs`, `hacks`, …) carry
|
|
14
|
+
its own `style`, `style_modifiers`, `size`, `quality`, and `model` — so
|
|
15
|
+
field notes, docs, and hacks render with distinct visual identities. A
|
|
16
|
+
file's collection is derived from its `_<name>` directory; precedence is
|
|
17
|
+
author overrides → collection styles → CLI flags → env → config → defaults.
|
|
18
|
+
|
|
19
|
+
## [0.2.2] - 2026-07-14
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- `jekyll preview-images` CLI flags were silently dropped: Mercenary keys the
|
|
24
|
+
options hash by the symbol given to `c.option`, but the argv translator
|
|
25
|
+
looked up the string form — so `--dry-run`, `--list-missing`,
|
|
26
|
+
`--provider`, etc. never reached the engine. Added a Ruby unit test for the
|
|
27
|
+
translation and a CI assertion that `--dry-run` writes nothing.
|
|
28
|
+
|
|
8
29
|
## [0.2.1] - 2026-07-13
|
|
9
30
|
|
|
10
31
|
### Fixed
|
data/README.md
CHANGED
|
@@ -93,12 +93,13 @@ preview_images:
|
|
|
93
93
|
collections_dir: '' # falls back to Jekyll's top-level collections_dir
|
|
94
94
|
front_matter_key: preview # key to read/write (jekyll-seo-tag sites: image)
|
|
95
95
|
authors_file: _data/authors.yml # per-author style overrides ('' disables)
|
|
96
|
+
collection_styles: {} # per-collection style/theme overrides (below)
|
|
96
97
|
assets_prefix: /assets # written paths omit this prefix ...
|
|
97
98
|
auto_prefix: true # ... and it is re-added for existence checks
|
|
98
99
|
```
|
|
99
100
|
|
|
100
|
-
Priority per file: author overrides →
|
|
101
|
-
`_config.yml` → built-in defaults.
|
|
101
|
+
Priority per file: author overrides → collection styles → CLI flags →
|
|
102
|
+
environment variables → `_config.yml` → built-in defaults.
|
|
102
103
|
|
|
103
104
|
Jekyll's own top-level `source:` key is honored automatically: on a site with
|
|
104
105
|
`source: pages` (content, assets, and `_data` under `pages/`), every disk path
|
|
@@ -129,6 +130,27 @@ preview_images:
|
|
|
129
130
|
# front_matter_key/preview + /assets prefixing are already the defaults
|
|
130
131
|
```
|
|
131
132
|
|
|
133
|
+
### Per-collection styles
|
|
134
|
+
|
|
135
|
+
Give each collection its own visual identity — tutorials render differently
|
|
136
|
+
from field notes, docs differently from reviews. Any of `style`,
|
|
137
|
+
`style_modifiers`, `size`, `quality`, and `model` can be set per collection;
|
|
138
|
+
everything else inherits the global config. A file's collection comes from its
|
|
139
|
+
`_<name>` directory; author overrides still win over collection styles.
|
|
140
|
+
|
|
141
|
+
```yaml
|
|
142
|
+
preview_images:
|
|
143
|
+
style: "retro pixel art" # global fallback
|
|
144
|
+
collection_styles:
|
|
145
|
+
posts:
|
|
146
|
+
style: "hand-inked editorial illustration, warm print tones"
|
|
147
|
+
docs:
|
|
148
|
+
style: "clean isometric technical diagram"
|
|
149
|
+
style_modifiers: "matte surfaces, labeled parts, blueprint accents"
|
|
150
|
+
hacks:
|
|
151
|
+
style: "circuit-board schematic, glowing traces"
|
|
152
|
+
```
|
|
153
|
+
|
|
132
154
|
### Per-author style overrides
|
|
133
155
|
|
|
134
156
|
If `authors_file` points at a YAML map, an author's `preview:` block overrides
|
|
@@ -72,7 +72,9 @@ module Zer0ImageGenerator
|
|
|
72
72
|
|
|
73
73
|
def build_argv(options)
|
|
74
74
|
SWITCHES.each_with_object([]) do |(flag, merc), argv|
|
|
75
|
-
|
|
75
|
+
# Mercenary keys the options hash by the symbol given to c.option.
|
|
76
|
+
value = options[merc.first]
|
|
77
|
+
value = options[merc.first.to_s] if value.nil?
|
|
76
78
|
next if value.nil? || value == false
|
|
77
79
|
|
|
78
80
|
if BOOLEANS.include?(flag)
|
|
@@ -17,6 +17,9 @@ knob with zer0-compatible defaults:
|
|
|
17
17
|
author overrides authors_file ("" disables) _data/authors.yml
|
|
18
18
|
collections collections (list, or "auto" to [posts]
|
|
19
19
|
discover from Jekyll's collections:)
|
|
20
|
+
collection styles collection_styles (map of {} (global style)
|
|
21
|
+
collection → style/style_modifiers/
|
|
22
|
+
size/quality/model overrides)
|
|
20
23
|
|
|
21
24
|
Architecture — Claude ORCHESTRATES, an image model RENDERS:
|
|
22
25
|
|
|
@@ -573,6 +576,22 @@ def read_authors(project_root: Path, authors_file: str) -> Dict[str, Any]:
|
|
|
573
576
|
return load_yaml_file(project_root / authors_file.strip().lstrip("/"))
|
|
574
577
|
|
|
575
578
|
|
|
579
|
+
# Settings fields an override block (author preview: / collection_styles:)
|
|
580
|
+
# may replace per file.
|
|
581
|
+
OVERRIDE_KEYS = ("style", "style_modifiers", "size", "quality", "model")
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
def _filter_override_block(block: Any) -> Dict[str, str]:
|
|
585
|
+
"""Keep only the recognized override keys with non-empty values."""
|
|
586
|
+
if not isinstance(block, dict):
|
|
587
|
+
return {}
|
|
588
|
+
return {
|
|
589
|
+
key: str(value).strip()
|
|
590
|
+
for key, value in block.items()
|
|
591
|
+
if key in OVERRIDE_KEYS and value is not None and str(value).strip()
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
|
|
576
595
|
def author_preview_overrides(authors: Dict[str, Any], author_key: Any) -> Dict[str, str]:
|
|
577
596
|
"""`preview:` override block for an author key (style/style_modifiers/size/
|
|
578
597
|
quality/model). `author:` may be a list or mapping in some posts — only a
|
|
@@ -582,15 +601,27 @@ def author_preview_overrides(authors: Dict[str, Any], author_key: Any) -> Dict[s
|
|
|
582
601
|
author = authors.get(author_key)
|
|
583
602
|
if not isinstance(author, dict):
|
|
584
603
|
return {}
|
|
585
|
-
|
|
586
|
-
|
|
604
|
+
return _filter_override_block(author.get("preview"))
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
def collection_of(path: Path) -> str:
|
|
608
|
+
"""The file's collection, from the nearest `_<name>` ancestor directory
|
|
609
|
+
(Jekyll's collection layout) — '' for files outside any collection."""
|
|
610
|
+
for parent in Path(path).resolve().parents:
|
|
611
|
+
name = parent.name
|
|
612
|
+
if name.startswith("_") and len(name) > 1:
|
|
613
|
+
return name[1:]
|
|
614
|
+
return ""
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
def collection_preview_overrides(
|
|
618
|
+
collection_styles: Dict[str, Any], collection: str
|
|
619
|
+
) -> Dict[str, str]:
|
|
620
|
+
"""`collection_styles:` override block for a collection — same keys as
|
|
621
|
+
author overrides, so each collection can carry its own visual theme."""
|
|
622
|
+
if not collection or not isinstance(collection_styles, dict):
|
|
587
623
|
return {}
|
|
588
|
-
return
|
|
589
|
-
key: str(value).strip()
|
|
590
|
-
for key, value in preview.items()
|
|
591
|
-
if key in ("style", "style_modifiers", "size", "quality", "model")
|
|
592
|
-
and value is not None and str(value).strip()
|
|
593
|
-
}
|
|
624
|
+
return _filter_override_block(collection_styles.get(collection))
|
|
594
625
|
|
|
595
626
|
|
|
596
627
|
def _env_flag(name: str) -> bool:
|
|
@@ -613,6 +644,7 @@ class Settings:
|
|
|
613
644
|
enabled: bool = True
|
|
614
645
|
collections: List[str] = field(default_factory=lambda: list(DEFAULTS["collections"]))
|
|
615
646
|
collections_dir: str = DEFAULTS["collections_dir"]
|
|
647
|
+
collection_styles: Dict[str, Any] = field(default_factory=dict)
|
|
616
648
|
front_matter_key: str = DEFAULTS["front_matter_key"]
|
|
617
649
|
authors_file: str = DEFAULTS["authors_file"]
|
|
618
650
|
prompt_engine: str = DEFAULTS["prompt_engine"]
|
|
@@ -703,6 +735,10 @@ def resolve_settings(args: argparse.Namespace, config: Dict[str, Any]) -> Settin
|
|
|
703
735
|
),
|
|
704
736
|
enabled=bool(cfg("enabled", True)),
|
|
705
737
|
collections=[str(c) for c in collections],
|
|
738
|
+
collection_styles=(
|
|
739
|
+
cfg("collection_styles", {})
|
|
740
|
+
if isinstance(cfg("collection_styles", {}), dict) else {}
|
|
741
|
+
),
|
|
706
742
|
collections_dir=str(collections_dir or "").strip().strip("/"),
|
|
707
743
|
front_matter_key=(
|
|
708
744
|
args.front_matter_key
|
|
@@ -748,20 +784,38 @@ def resolve_settings(args: argparse.Namespace, config: Dict[str, Any]) -> Settin
|
|
|
748
784
|
return settings
|
|
749
785
|
|
|
750
786
|
|
|
751
|
-
def
|
|
752
|
-
|
|
753
|
-
|
|
787
|
+
def apply_preview_overrides(
|
|
788
|
+
settings: Settings, *override_blocks: Dict[str, str]
|
|
789
|
+
) -> Settings:
|
|
790
|
+
"""Per-file settings copy with override blocks applied in order — later
|
|
791
|
+
blocks win (pass collection styles first, author overrides on top)."""
|
|
792
|
+
merged: Dict[str, str] = {}
|
|
793
|
+
for block in override_blocks:
|
|
794
|
+
merged.update(block or {})
|
|
795
|
+
if not merged:
|
|
754
796
|
return settings
|
|
755
797
|
return replace(
|
|
756
798
|
settings,
|
|
757
|
-
|
|
758
|
-
style_modifiers=overrides.get("style_modifiers", settings.style_modifiers),
|
|
759
|
-
size=overrides.get("size", settings.size),
|
|
760
|
-
quality=overrides.get("quality", settings.quality),
|
|
761
|
-
model=overrides.get("model", settings.model),
|
|
799
|
+
**{key: merged.get(key, getattr(settings, key)) for key in OVERRIDE_KEYS},
|
|
762
800
|
)
|
|
763
801
|
|
|
764
802
|
|
|
803
|
+
def file_preview_settings(settings: Settings, cf: "ContentFile",
|
|
804
|
+
authors: Dict[str, Any]) -> Settings:
|
|
805
|
+
"""The per-file settings: global → collection_styles[collection] →
|
|
806
|
+
author preview: block (most specific wins)."""
|
|
807
|
+
coll_overrides = collection_preview_overrides(
|
|
808
|
+
settings.collection_styles, collection_of(cf.path))
|
|
809
|
+
author_overrides = author_preview_overrides(authors, cf.author)
|
|
810
|
+
result = apply_preview_overrides(settings, coll_overrides, author_overrides)
|
|
811
|
+
if coll_overrides:
|
|
812
|
+
info(f" ↳ Collection '{collection_of(cf.path)}' preview style applied")
|
|
813
|
+
if author_overrides:
|
|
814
|
+
info(f" ↳ Author '{cf.author}' preview overrides applied "
|
|
815
|
+
f"({settings.authors_file})")
|
|
816
|
+
return result
|
|
817
|
+
|
|
818
|
+
|
|
765
819
|
def model_family(model: str) -> Optional[str]:
|
|
766
820
|
m = (model or "").strip().lower()
|
|
767
821
|
for prefix, family in MODEL_FAMILIES.items():
|
|
@@ -2025,13 +2079,8 @@ class Runner:
|
|
|
2025
2079
|
return
|
|
2026
2080
|
out_base = self.root / settings.output_dir / slug
|
|
2027
2081
|
|
|
2028
|
-
#
|
|
2029
|
-
file_settings =
|
|
2030
|
-
settings, author_preview_overrides(self.authors, cf.author)
|
|
2031
|
-
)
|
|
2032
|
-
if file_settings is not settings:
|
|
2033
|
-
info(f" ↳ Author '{cf.author}' preview overrides applied "
|
|
2034
|
-
f"({settings.authors_file})")
|
|
2082
|
+
# Style overrides apply only once an image/prompt is actually built.
|
|
2083
|
+
file_settings = file_preview_settings(settings, cf, self.authors)
|
|
2035
2084
|
|
|
2036
2085
|
# ---- Analyze: Claude reads the article and writes the art brief ----
|
|
2037
2086
|
base_prompt = build_prompt(cf, file_settings)
|
|
@@ -2114,9 +2163,7 @@ class Runner:
|
|
|
2114
2163
|
return
|
|
2115
2164
|
|
|
2116
2165
|
info(f"Enhancing preview for: {cf.title}")
|
|
2117
|
-
file_settings =
|
|
2118
|
-
settings, author_preview_overrides(self.authors, cf.author)
|
|
2119
|
-
)
|
|
2166
|
+
file_settings = file_preview_settings(settings, cf, self.authors)
|
|
2120
2167
|
prompt = build_enhance_prompt(cf, file_settings)
|
|
2121
2168
|
debug(f"Enhancement prompt: {prompt[:400]}...")
|
|
2122
2169
|
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: zer0-image-generator
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Amr Abdel
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: jekyll
|
|
@@ -60,7 +59,6 @@ metadata:
|
|
|
60
59
|
changelog_uri: https://github.com/bamr87/zer0-image-generator/blob/main/CHANGELOG.md
|
|
61
60
|
bug_tracker_uri: https://github.com/bamr87/zer0-image-generator/issues
|
|
62
61
|
rubygems_mfa_required: 'true'
|
|
63
|
-
post_install_message:
|
|
64
62
|
rdoc_options: []
|
|
65
63
|
require_paths:
|
|
66
64
|
- lib
|
|
@@ -76,8 +74,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
76
74
|
version: '0'
|
|
77
75
|
requirements:
|
|
78
76
|
- Python 3.9+ with PyYAML (python3 -m pip install pyyaml)
|
|
79
|
-
rubygems_version:
|
|
80
|
-
signing_key:
|
|
77
|
+
rubygems_version: 4.0.11
|
|
81
78
|
specification_version: 4
|
|
82
79
|
summary: AI preview/social images for any Jekyll site — Claude directs and reviews,
|
|
83
80
|
an image model renders.
|