textus 0.14.0 → 0.14.1

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: 24eda2a2cfec1884eb3c675189493827b6c0f43d5765922dbee06b70fbdc9685
4
- data.tar.gz: 84e3850666541900f4c3b99f71c59b73dd142c7001f80fa9e8233d38f984719e
3
+ metadata.gz: 7f46590895cb1b7dccf71efbb6cc7632ac1054e545323478e831875c7b7bf621
4
+ data.tar.gz: e84857018c0bf4e61e6e60a476191e04dd2fe470f476b27860deaf59611993ab
5
5
  SHA512:
6
- metadata.gz: d68ad56bc7891aefda10e47234f1bbd1efbfd75b576f7981c3374641efbd306dc66947b382b74c70da24bc58122090c3e0789c33b1a8291aa1005b9e6c952e2e
7
- data.tar.gz: fd1c1cefad648636b4fb5aeba100d81c66472f8e1b50d07ba2239607c19179842902abf0b76925786c66868049acfd507a9a76bd07e39026dd2aa0d261ebfdad
6
+ metadata.gz: e21ff9e7d9a9cfe93ab3865646b53bbea4647f0c7ace9d329e7ccec13a2ce7343568df836bd25fccfa19c39d4384a2d522d827e5d01b7ebbdd0b2ab3098978be
7
+ data.tar.gz: 700e831af5ba5c13a9e66984e59ee4774def27e1c098b7dfd2d6e942b6cd064b7e3e3d1480faa110417ede67aa229d0b7bfea063f8f5f59e91bcaa0dce282355
data/CHANGELOG.md CHANGED
@@ -9,6 +9,23 @@ The **gem version** (`0.x.y`) is distinct from the **protocol version**
9
9
  bump is a breaking change that requires a store migration; the gem version
10
10
  tracks both additive improvements and breaking protocol bumps independently.
11
11
 
12
+ ## 0.14.1 — 2026-05-26
13
+
14
+ ### Changed
15
+
16
+ - Build pipeline now skips rewriting a built artifact when the only
17
+ difference from the existing file on disk would be the freshly-stamped
18
+ `generated_at` (markdown: `generated.at`) timestamp. Stores under git
19
+ versioning no longer churn on every `textus build` (#52).
20
+ - Strict policy: any other byte difference — changed `from`,
21
+ `template`, `reduce`, body content — still triggers a write. Text
22
+ format falls back to plain byte-equality (no timestamp to normalize).
23
+
24
+ ### Internal
25
+
26
+ - Extracted `Manifest.check_version!` to dedupe the parse/load version
27
+ guard (#51).
28
+
12
29
  ## 0.14.0 — 2026-05-26
13
30
 
14
31
  ### Breaking (Ruby API only — CLI JSON output unchanged)
@@ -19,6 +19,35 @@ module Textus
19
19
  end
20
20
  end
21
21
 
22
+ # Replaces the freshly-stamped timestamp inside `new_bytes` with the
23
+ # timestamp pulled from `old_bytes` (same format). Returns the rewritten
24
+ # bytes, or nil if either side lacks a parseable timestamp.
25
+ module IdempotentWrite
26
+ def self.rewrite_with_prior_timestamp(new_bytes:, old_bytes:, format:)
27
+ prior = extract_timestamp(old_bytes, format)
28
+ fresh = extract_timestamp(new_bytes, format)
29
+ return nil unless prior && fresh
30
+ return new_bytes if prior == fresh
31
+
32
+ new_bytes.sub(fresh, prior)
33
+ end
34
+
35
+ def self.extract_timestamp(bytes, format)
36
+ case format
37
+ when "markdown"
38
+ parsed = Entry.for_format("markdown").parse(bytes)
39
+ parsed.dig("_meta", "generated", "at")
40
+ when "json", "yaml"
41
+ parsed = Entry.for_format(format).parse(bytes)
42
+ parsed.dig("_meta", "generated_at")
43
+ else # rubocop:disable Style/EmptyElse
44
+ nil
45
+ end
46
+ rescue Textus::BadFrontmatter
47
+ nil
48
+ end
49
+ end
50
+
22
51
  module Pipeline
23
52
  def self.renderers
24
53
  @renderers ||= {
@@ -44,13 +73,28 @@ module Textus
44
73
  raise UsageError.new("builder: unsupported format #{mentry.format.inspect} for '#{mentry.key}'")
45
74
  bytes = klass.new(template_loader: template_loader).call(mentry: mentry, data: data)
46
75
 
47
- # 3. Write
76
+ # 3. Write (idempotent: skip if only generated_at would differ)
48
77
  target_path = Key::Path.resolve(store.manifest, mentry)
49
78
  FileUtils.mkdir_p(File.dirname(target_path))
50
- File.binwrite(target_path, bytes)
79
+ write_if_changed(target_path, bytes, mentry.format)
51
80
 
52
81
  target_path
53
82
  end
83
+
84
+ def self.write_if_changed(target_path, bytes, format)
85
+ if File.exist?(target_path)
86
+ old_bytes = File.binread(target_path)
87
+ if format == "text"
88
+ return if old_bytes == bytes
89
+ else
90
+ rewritten = IdempotentWrite.rewrite_with_prior_timestamp(
91
+ new_bytes: bytes, old_bytes: old_bytes, format: format,
92
+ )
93
+ return if rewritten && rewritten == old_bytes
94
+ end
95
+ end
96
+ File.binwrite(target_path, bytes)
97
+ end
54
98
  end
55
99
  end
56
100
  end
@@ -39,14 +39,7 @@ module Textus
39
39
 
40
40
  def self.parse(yaml_text, root: ".")
41
41
  raw = YAML.safe_load(yaml_text, aliases: false)
42
- unless raw["version"] == PROTOCOL
43
- raise BadFrontmatter.new(
44
- "<string>",
45
- "unsupported manifest version #{raw["version"].inspect}; expected #{PROTOCOL.inspect}",
46
- hint: version_hint_for(raw["version"]),
47
- )
48
- end
49
-
42
+ check_version!(raw, "<string>")
50
43
  new(root, raw)
51
44
  end
52
45
 
@@ -55,17 +48,21 @@ module Textus
55
48
  raise IoError.new("manifest not found: #{manifest_path}") unless File.exist?(manifest_path)
56
49
 
57
50
  raw = YAML.safe_load_file(manifest_path, aliases: false)
58
- unless raw["version"] == PROTOCOL
59
- raise BadFrontmatter.new(
60
- manifest_path,
61
- "unsupported manifest version #{raw["version"].inspect}; expected #{PROTOCOL.inspect}",
62
- hint: version_hint_for(raw["version"]),
63
- )
64
- end
65
-
51
+ check_version!(raw, manifest_path)
66
52
  new(root, raw)
67
53
  end
68
54
 
55
+ def self.check_version!(raw, source)
56
+ return if raw["version"] == PROTOCOL
57
+
58
+ raise BadFrontmatter.new(
59
+ source,
60
+ "unsupported manifest version #{raw["version"].inspect}; expected #{PROTOCOL.inspect}",
61
+ hint: version_hint_for(raw["version"]),
62
+ )
63
+ end
64
+ private_class_method :check_version!
65
+
69
66
  def initialize(root, raw)
70
67
  @root = root
71
68
  @raw = raw
@@ -1,4 +1,4 @@
1
1
  module Textus
2
- VERSION = "0.14.0"
2
+ VERSION = "0.14.1"
3
3
  PROTOCOL = "textus/3"
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: textus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.14.0
4
+ version: 0.14.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrick