Class: Sourcerer::Sync::Cast

Inherits:
Object
  • Object
show all
Defined in:
lib/sourcerer/sync/cast.rb

Overview

Synchronizes canonical blocks from a prime template into one target file.

See Sourcerer::Sync for the high-level interface and the project README for usage examples and a full description of the Sync/Cast model.

Defined Under Namespace

Classes: CastResult

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(prime_path, target_path, data:, canonical_prefix:, tag_syntax_start:, tag_syntax_end:, comment_syntax_patterns:, dry_run:) ⇒ Cast

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns a new instance of Cast.



112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/sourcerer/sync/cast.rb', line 112

def initialize prime_path, target_path,
  data:, canonical_prefix:,
  tag_syntax_start:, tag_syntax_end:, comment_syntax_patterns:,
  dry_run:
  @prime_path = prime_path
  @target_path = target_path
  @data = data
  @canonical_prefix = canonical_prefix
  @tag_syntax_start = tag_syntax_start
  @dry_run = dry_run
  @tag_patterns = BlockParser.build_tag_patterns(
    tag_syntax_start, tag_syntax_end, comment_syntax_patterns)
end

Class Method Details

.init(prime_path, target_path, data: {}, dry_run: false) ⇒ CastResult

Bootstrap a new target file from the prime template.

During init the entire prime is rendered through Liquid before writing;

during sync only canonical block content is rendered.
See the project README for a full description of init vs sync semantics.

Parameters:

  • prime_path (String)

    Path to the prime template file

  • target_path (String)

    Path to the target file to create

  • data (Hash) (defaults to: {})

    Liquid variables used when rendering

  • dry_run (Boolean) (defaults to: false)

    When true, return rendered content in ‘diff` but do not write.

Returns:



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/sourcerer/sync/cast.rb', line 73

def self.init prime_path, target_path, data: {}, dry_run: false
  segments = parse_prime_segments(File.read(prime_path))

  liquid_preamble = segments
                    .find { |s| s.is_a?(BlockParser::Block) && s.tag == '_liquid' }
                    &.content.to_s

  clean_text = segments
               .reject { |s| s.is_a?(BlockParser::Block) && s.tag.start_with?('_') }
               .map { |s| s.is_a?(BlockParser::Block) ? "#{s.open_line}#{s.content}#{s.close_line}" : s.content }
               .join

  rendered = if data.empty? && liquid_preamble.empty?
               clean_text
             else
               # Wrap the preamble in a silent capture block so the assign statements
               # populate variables without emitting any whitespace into the output.
               full = if liquid_preamble.empty?
                        clean_text
                      else
                        "{%- capture __preamble__ -%}#{liquid_preamble}{%- endcapture -%}#{clean_text}"
                      end
               render_liquid_string(full, data)
             end

  unless dry_run
    FileUtils.mkdir_p(File.dirname(File.expand_path(target_path)))
    File.write(target_path, rendered)
  end

  CastResult.new(
    target_path: target_path,
    applied_changes: [],
    warnings: [],
    errors: [],
    diff: dry_run ? rendered : nil)
end

.parse_prime_segments(text) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Parse a prime template using the default tag patterns. Shared by init and strip_meta_blocks to avoid repeating the build_tag_patterns / parse boilerplate.



207
208
209
210
211
212
213
# File 'lib/sourcerer/sync/cast.rb', line 207

def self.parse_prime_segments text
  tag_patterns = BlockParser.build_tag_patterns(
    BlockParser::DEFAULT_TAG_SYNTAX_START,
    BlockParser::DEFAULT_TAG_SYNTAX_END,
    BlockParser::DEFAULT_COMMENT_SYNTAX_PATTERNS)
  BlockParser.parse(text, canonical_prefix: '', tag_patterns: tag_patterns)
end

.render_liquid_string(content, data) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



180
181
182
183
184
185
186
187
188
189
# File 'lib/sourcerer/sync/cast.rb', line 180

def self.render_liquid_string content, data
  require_relative '../jekyll'
  require_relative '../jekyll/liquid/filters'
  require_relative '../jekyll/liquid/tags'
  require 'liquid' unless defined?(Liquid::Template)
  Sourcerer::Jekyll.initialize_liquid_runtime

  template = Liquid::Template.parse(content)
  template.render(data.transform_keys(&:to_s))
end

.strip_meta_blocks(text) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Remove every underscore-prefixed meta block (_skip, _liquid, etc.) from a prime text before it is written to a target during init. These blocks carry template instructions or Liquid context that are only meaningful during the prime→target rendering pass, not in the output file.



196
197
198
199
200
201
# File 'lib/sourcerer/sync/cast.rb', line 196

def self.strip_meta_blocks text
  parse_prime_segments(text)
    .reject { |s| s.is_a?(BlockParser::Block) && s.tag.start_with?('_') }
    .map { |s| s.is_a?(BlockParser::Block) ? "#{s.open_line}#{s.content}#{s.close_line}" : s.content }
    .join
end

.sync(prime_path, target_path, data: {}, canonical_prefix: BlockParser::DEFAULT_CANONICAL_PREFIX, tag_syntax_start: BlockParser::DEFAULT_TAG_SYNTAX_START, tag_syntax_end: BlockParser::DEFAULT_TAG_SYNTAX_END, comment_syntax_patterns: BlockParser::DEFAULT_COMMENT_SYNTAX_PATTERNS, dry_run: false) ⇒ CastResult

Synchronize canonical blocks from ‘prime_path` into `target_path`.

Parameters:

  • prime_path (String)

    Path to the prime template file

  • target_path (String)

    Path to the target file

  • data (Hash) (defaults to: {})

    Liquid variables used when rendering block content

  • canonical_prefix (String) (defaults to: BlockParser::DEFAULT_CANONICAL_PREFIX)

    Tag prefix that marks managed blocks

  • tag_syntax_start (String) (defaults to: BlockParser::DEFAULT_TAG_SYNTAX_START)

    Opening tag marker template (see BlockParser::DEFAULT_TAG_SYNTAX_START)

  • tag_syntax_end (String) (defaults to: BlockParser::DEFAULT_TAG_SYNTAX_END)

    Closing tag marker template (see BlockParser::DEFAULT_TAG_SYNTAX_END)

  • comment_syntax_patterns (Array<String>) (defaults to: BlockParser::DEFAULT_COMMENT_SYNTAX_PATTERNS)

    Comment-wrapper templates (see BlockParser::DEFAULT_COMMENT_SYNTAX_PATTERNS)

  • dry_run (Boolean) (defaults to: false)

    When true, compute the diff but do not write

Returns:



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/sourcerer/sync/cast.rb', line 44

def self.sync prime_path, target_path,
  data: {},
  canonical_prefix: BlockParser::DEFAULT_CANONICAL_PREFIX,
  tag_syntax_start: BlockParser::DEFAULT_TAG_SYNTAX_START,
  tag_syntax_end: BlockParser::DEFAULT_TAG_SYNTAX_END,
  comment_syntax_patterns: BlockParser::DEFAULT_COMMENT_SYNTAX_PATTERNS,
  dry_run: false
  new(
    prime_path, target_path,
    data: data,
    canonical_prefix: canonical_prefix,
    tag_syntax_start: tag_syntax_start,
    tag_syntax_end: tag_syntax_end,
    comment_syntax_patterns: comment_syntax_patterns,
    dry_run: dry_run).run_sync
end

Instance Method Details

#run_syncObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/sourcerer/sync/cast.rb', line 127

def run_sync
  prime_text = File.read(@prime_path)
  target_text = File.read(@target_path)

  # Parse with canonical_prefix: '' so that ALL tagged regions -- including
  # the non-canonical _liquid preamble block -- surface as Block objects
  # rather than being swallowed into TextSegments.
  prime_segments  = BlockParser.parse(
    prime_text,
    canonical_prefix: '',
    tag_patterns: @tag_patterns)
  target_segments = BlockParser.parse(
    target_text,
    canonical_prefix: '',
    tag_patterns: @tag_patterns)

  # Extract the _liquid preamble from the prime (non-canonical; not synced as a
  # canonical block but used to carry Liquid variable context to all rendered content).
  prime_liquid_block = prime_segments.find { |s| s.is_a?(BlockParser::Block) && s.tag == '_liquid' }
  liquid_preamble = prime_liquid_block&.content.to_s

  prime_blocks = BlockParser.extract_canonical(prime_segments, canonical_prefix: @canonical_prefix)
  target_blocks, errors = validate_target_canonical(target_segments)

  if errors.any?
    return CastResult.new(
      target_path: @target_path,
      applied_changes: [],
      warnings: [],
      errors: errors,
      diff: nil)
  end

  warnings = collect_warnings(prime_blocks, target_blocks, target_text)
  new_segments, applied_changes = apply_prime_blocks(
    target_segments, prime_blocks,
    prime_liquid_block: prime_liquid_block,
    liquid_preamble: liquid_preamble)

  new_text = reconstruct(new_segments)
  diff = generate_diff(target_text, new_text) if applied_changes.any? || @dry_run

  File.write(@target_path, new_text) unless @dry_run

  CastResult.new(
    target_path: @target_path,
    applied_changes: @dry_run ? [] : applied_changes,
    warnings: warnings,
    errors: [],
    diff: diff)
end