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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 36da81ce0baf2b37bb861a7fe2f03d62c37c667d8a0754a9cc1ee95ee6afc827
4
- data.tar.gz: a18923ae9adf0a2b38e2dca873bfaaba9e1e1437249e68ca5b70cc7dc59c6588
3
+ metadata.gz: 24975e688896196e8e883c15ed17d09e5856a9b68f18b47b347bc61d701cc265
4
+ data.tar.gz: da2d123ed3067f36c2e5962bd3a351a9d1ae48e1fd97bfe6dbcccb23aac53e52
5
5
  SHA512:
6
- metadata.gz: 63b595b05dddeaf526df975f62ad5d8644b4d9e8571398a717b2207a5927a1c5f1216db0ec71aab7c00f2f7077d14e9a430b57ea1650ee882c35d7c2579c3382
7
- data.tar.gz: dfe915ca69f2bb13a3023c63641755e96498634eb07d4779721158361e871a7433b701fa775f8ecfba358a9c4a73a2c5698c46dcacc8231968b15f1d5857df7b
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 → CLI flagsenvironment variables
101
- `_config.yml` → built-in defaults.
101
+ Priority per file: author overrides → collection stylesCLI 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
- value = options[merc.first.to_s]
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
- preview = author.get("preview")
586
- if not isinstance(preview, dict):
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 apply_author_overrides(settings: Settings, overrides: Dict[str, str]) -> Settings:
752
- """Per-file settings copy with the author's preview block applied on top."""
753
- if not overrides:
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
- style=overrides.get("style", settings.style),
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
- # Author overrides apply only once an image/prompt is actually built.
2029
- file_settings = apply_author_overrides(
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 = apply_author_overrides(
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
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Zer0ImageGenerator
4
- VERSION = "0.2.1"
4
+ VERSION = "0.3.0"
5
5
  end
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.2.1
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: 2026-07-14 00:00:00.000000000 Z
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: 3.0.3.1
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.