releasehx 0.1.2 → 0.2.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/README.adoc +363 -330
- data/build/docs/_config.yml +1 -0
- data/build/docs/_release_index.adoc +3 -2
- data/build/docs/config-reference.adoc +197 -10
- data/build/docs/config-reference.json +56 -7
- data/build/docs/index.adoc +315 -59
- data/build/docs/landing.adoc +1 -1
- data/build/docs/manpage.adoc +2 -2
- data/build/docs/release-procedure.adoc +365 -0
- data/build/docs/release-procedure.html +87 -0
- data/build/docs/releasehx.1 +17 -5
- data/build/docs/sample-config.yml +14 -7
- data/lib/releasehx/cli.rb +5 -2
- data/lib/releasehx/configuration.rb +0 -1
- data/lib/releasehx/generated.rb +1 -1
- data/lib/releasehx/mcp/assets/agent-config-guide.md +1 -1
- data/lib/releasehx/mcp/assets/config-def.yml +122 -6
- data/lib/releasehx/mcp/assets/config-reference.adoc +197 -10
- data/lib/releasehx/mcp/assets/config-reference.json +56 -7
- data/lib/releasehx/mcp/assets/sample-config.yml +14 -7
- data/lib/releasehx/mcp/server.rb +0 -1
- data/lib/releasehx/ops/enrich_ops.rb +161 -55
- data/lib/releasehx/ops/template_ops.rb +1 -1
- data/lib/releasehx/rhyml/adapter.rb +0 -3
- data/lib/releasehx/rhyml/templates/bootstrap-overrides.css +15 -0
- data/lib/releasehx/rhyml/templates/changelog.adoc.liquid +2 -0
- data/lib/releasehx/rhyml/templates/changelog.html.liquid +6 -4
- data/lib/releasehx/rhyml/templates/changelog.md.liquid +1 -0
- data/lib/releasehx/rhyml/templates/embedded.css.liquid +263 -0
- data/lib/releasehx/rhyml/templates/entry.adoc.liquid +1 -0
- data/lib/releasehx/rhyml/templates/entry.html.liquid +21 -20
- data/lib/releasehx/rhyml/templates/entry.md.liquid +15 -21
- data/lib/releasehx/rhyml/templates/head-parser.liquid +6 -2
- data/lib/releasehx/rhyml/templates/header.liquid +13 -4
- data/lib/releasehx/rhyml/templates/history.html.liquid +152 -33
- data/lib/releasehx/rhyml/templates/metadata-entry.adoc.liquid +83 -38
- data/lib/releasehx/rhyml/templates/metadata-entry.html.liquid +60 -1
- data/lib/releasehx/rhyml/templates/metadata-entry.md.liquid +65 -113
- data/lib/releasehx/rhyml/templates/metadata-note.adoc.liquid +83 -38
- data/lib/releasehx/rhyml/templates/metadata-note.html.liquid +59 -22
- data/lib/releasehx/rhyml/templates/metadata-note.md.liquid +68 -23
- data/lib/releasehx/rhyml/templates/note.html.liquid +25 -19
- data/lib/releasehx/rhyml/templates/note.md.liquid +44 -26
- data/lib/releasehx/rhyml/templates/release-notes.adoc.liquid +2 -0
- data/lib/releasehx/rhyml/templates/release-notes.html.liquid +6 -4
- data/lib/releasehx/rhyml/templates/release-notes.md.liquid +1 -0
- data/lib/releasehx/rhyml/templates/release.adoc.liquid +2 -0
- data/lib/releasehx/rhyml/templates/release.md.liquid +8 -7
- data/lib/releasehx/rhyml/templates/rhyml-change.yaml.liquid +36 -36
- data/lib/releasehx/rhyml/templates/wrapper.html.liquid +103 -0
- data/lib/releasehx/sgyml/helpers.rb +0 -2
- data/lib/releasehx/transforms/adf_to_markdown.rb +1 -1
- data/lib/releasehx/version.rb +0 -2
- data/lib/releasehx.rb +2 -2
- data/specs/data/config-def.yml +122 -6
- metadata +48 -25
- data/build/docs/schemagraphy_readme.html +0 -0
- data/build/docs/sourcerer_readme.html +0 -46
- data/lib/schemagraphy/attribute_resolver.rb +0 -48
- data/lib/schemagraphy/cfgyml/definition.rb +0 -90
- data/lib/schemagraphy/cfgyml/doc_builder.rb +0 -52
- data/lib/schemagraphy/cfgyml/path_reference.rb +0 -24
- data/lib/schemagraphy/data_query/json_pointer.rb +0 -42
- data/lib/schemagraphy/loader.rb +0 -59
- data/lib/schemagraphy/regexp_utils.rb +0 -235
- data/lib/schemagraphy/safe_expression.rb +0 -189
- data/lib/schemagraphy/schema_utils.rb +0 -124
- data/lib/schemagraphy/tag_utils.rb +0 -32
- data/lib/schemagraphy/templating.rb +0 -104
- data/lib/schemagraphy.rb +0 -17
- data/lib/sourcerer/builder.rb +0 -120
- data/lib/sourcerer/jekyll/bootstrapper.rb +0 -78
- data/lib/sourcerer/jekyll/liquid/file_system.rb +0 -74
- data/lib/sourcerer/jekyll/liquid/filters.rb +0 -215
- data/lib/sourcerer/jekyll/liquid/tags.rb +0 -44
- data/lib/sourcerer/jekyll/monkeypatches.rb +0 -73
- data/lib/sourcerer/jekyll.rb +0 -26
- data/lib/sourcerer/plaintext_converter.rb +0 -75
- data/lib/sourcerer/templating.rb +0 -190
- data/lib/sourcerer.rb +0 -322
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'prism'
|
|
4
|
-
require 'timeout'
|
|
5
|
-
|
|
6
|
-
module SchemaGraphy
|
|
7
|
-
# Provides a simple, deny-by-exception sandbox for mapping expressions.
|
|
8
|
-
# It validates code by walking the Abstract Syntax Tree (AST) and blocking
|
|
9
|
-
# known dangerous operations, rather than attempting to allowlist safe ones.
|
|
10
|
-
class AstGate
|
|
11
|
-
# A list of dangerous bareword methods that are blocked.
|
|
12
|
-
BLOCKED_BAREWORDS = %w[
|
|
13
|
-
eval instance_eval class_eval module_eval binding
|
|
14
|
-
require require_relative load autoload
|
|
15
|
-
system exec spawn fork backtick `
|
|
16
|
-
open ObjectSpace GC Thread Process at_exit
|
|
17
|
-
].freeze
|
|
18
|
-
|
|
19
|
-
# A list of AST node types that are explicitly disallowed.
|
|
20
|
-
DISALLOWED_NODES = %i[
|
|
21
|
-
# Definitions and meta-programming
|
|
22
|
-
def_node class_node module_node define_node alias_node undef_node
|
|
23
|
-
# Globals and constants paths
|
|
24
|
-
global_variable_read_node constant_path_node
|
|
25
|
-
# Shell and backticks
|
|
26
|
-
x_string_node interpolated_x_string_node
|
|
27
|
-
].freeze
|
|
28
|
-
|
|
29
|
-
# A list of constants that are considered dangerous and are blocked.
|
|
30
|
-
DANGEROUS_CONSTANTS = %w[
|
|
31
|
-
Kernel Object Module Class File FileUtils IO Dir Process Open3 PTY Thread
|
|
32
|
-
SystemSignal Signal Gem Net HTTP TCPSocket UDPSocket Socket ObjectSpace GC
|
|
33
|
-
].freeze
|
|
34
|
-
|
|
35
|
-
# Validates the given code by parsing it and walking the AST.
|
|
36
|
-
#
|
|
37
|
-
# @param code [String] The Ruby code to validate.
|
|
38
|
-
# @param context_keys [Array<Symbol>] A list of keys available in the execution context.
|
|
39
|
-
# @raise [SyntaxError] if the code has syntax errors.
|
|
40
|
-
# @raise [SecurityError] if the code contains disallowed operations.
|
|
41
|
-
def self.validate! code, context_keys: []
|
|
42
|
-
result = Prism.parse(code)
|
|
43
|
-
raise SyntaxError, result.errors.map(&:message).join(', ') if result.errors.any?
|
|
44
|
-
|
|
45
|
-
walk(result.value, context_keys: context_keys)
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# @api private
|
|
49
|
-
# Recursively walks the AST, checking for disallowed nodes and operations.
|
|
50
|
-
#
|
|
51
|
-
# @param node [Prism::Node] The current AST node.
|
|
52
|
-
# @param context_keys [Array<Symbol>] A list of keys available in the execution context.
|
|
53
|
-
# @raise [SecurityError] if a disallowed operation is found.
|
|
54
|
-
def self.walk node, context_keys: []
|
|
55
|
-
return unless node.is_a?(Prism::Node)
|
|
56
|
-
|
|
57
|
-
type = node.type
|
|
58
|
-
raise SecurityError, "node not allowed: #{type}" if DISALLOWED_NODES.include?(type)
|
|
59
|
-
|
|
60
|
-
case node
|
|
61
|
-
when Prism::CallNode
|
|
62
|
-
# Block dangerous barewords (system, eval, etc.)
|
|
63
|
-
if node.receiver.nil? && BLOCKED_BAREWORDS.include?(node.name.to_s)
|
|
64
|
-
raise SecurityError, "method not allowed: #{node.name}"
|
|
65
|
-
end
|
|
66
|
-
# Block dangerous constants and constant paths
|
|
67
|
-
if node.receiver.is_a?(Prism::ConstantReadNode) && DANGEROUS_CONSTANTS.include?(node.receiver.name.to_s)
|
|
68
|
-
raise SecurityError, "unsafe constant: #{node.receiver.name}"
|
|
69
|
-
end
|
|
70
|
-
raise SecurityError, 'unsafe constant path' if node.receiver.is_a?(Prism::ConstantPathNode)
|
|
71
|
-
|
|
72
|
-
when Prism::ConstantReadNode
|
|
73
|
-
# Allow only core Ruby constants defined in SafeTransform
|
|
74
|
-
const_name = node.name.to_s
|
|
75
|
-
unless SafeTransform::CORE_CONSTANTS.key?(const_name.to_sym)
|
|
76
|
-
raise SecurityError, "constant not allowed: #{const_name}"
|
|
77
|
-
end
|
|
78
|
-
when Prism::ConstantPathNode, Prism::GlobalVariableReadNode
|
|
79
|
-
raise SecurityError, 'constant paths and global variables are not allowed'
|
|
80
|
-
when Prism::DefNode, Prism::ClassNode, Prism::ModuleNode
|
|
81
|
-
raise SecurityError, 'method, class, and module definitions are not allowed'
|
|
82
|
-
when Prism::BackReferenceReadNode, Prism::XStringNode, Prism::InterpolatedXStringNode
|
|
83
|
-
raise SecurityError, 'shell commands and backticks are not allowed'
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
node.child_nodes.each { |child| walk(child, context_keys: context_keys) if child }
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
# Provides a sandboxed environment for executing Ruby code.
|
|
91
|
-
# Inherits from `BasicObject` for a minimal namespace and uses `instance_eval`
|
|
92
|
-
# to run code within its own context. All code is validated by {AstGate} before execution.
|
|
93
|
-
class SafeTransform < BasicObject
|
|
94
|
-
# A minimal set of core Ruby constants exposed to the sandboxed environment.
|
|
95
|
-
CORE_CONSTANTS = {
|
|
96
|
-
Array: ::Array,
|
|
97
|
-
Hash: ::Hash,
|
|
98
|
-
String: ::String,
|
|
99
|
-
Integer: ::Integer,
|
|
100
|
-
Float: ::Float,
|
|
101
|
-
TrueClass: ::TrueClass,
|
|
102
|
-
FalseClass: ::FalseClass,
|
|
103
|
-
NilClass: ::NilClass,
|
|
104
|
-
Symbol: ::Symbol,
|
|
105
|
-
Numeric: ::Numeric,
|
|
106
|
-
Regexp: ::Regexp
|
|
107
|
-
}.freeze
|
|
108
|
-
|
|
109
|
-
CORE_CONSTANTS.each do |name, ref|
|
|
110
|
-
const_set(name, ref) unless const_defined?(name, false)
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
# @param context [Hash] A hash of data to be made available in the sandbox.
|
|
114
|
-
def initialize context = {}
|
|
115
|
-
@context = context
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
# Executes the given code within the sandboxed environment.
|
|
119
|
-
#
|
|
120
|
-
# @param code [String] The Ruby code to execute.
|
|
121
|
-
# @return [Object] The result of the executed code.
|
|
122
|
-
# @raise [Timeout::Error] if the execution time exceeds the limit.
|
|
123
|
-
# @raise [SecurityError] if the code contains disallowed operations.
|
|
124
|
-
def transform code
|
|
125
|
-
::Timeout.timeout(0.25) do
|
|
126
|
-
AstGate.validate!(code, context_keys: @context.keys)
|
|
127
|
-
instance_eval(code)
|
|
128
|
-
end
|
|
129
|
-
rescue ::Timeout::Error
|
|
130
|
-
::Kernel.raise ::StandardError, 'transform timed out'
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
# Adds a key-value pair to the execution context.
|
|
134
|
-
#
|
|
135
|
-
# @param key [String, Symbol] The key to add.
|
|
136
|
-
# @param value [Object] The value to associate with the key.
|
|
137
|
-
def add_context key, value
|
|
138
|
-
@context[key.to_s] = value
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
# Safely traverses a nested object using a dot-separated path.
|
|
142
|
-
#
|
|
143
|
-
# @param obj [Object] The object to traverse.
|
|
144
|
-
# @param path [String] The dot-separated path (e.g., "a.b.c").
|
|
145
|
-
# @return [Object, nil] The value at the specified path, or `nil`.
|
|
146
|
-
def dig_path obj, path
|
|
147
|
-
keys = path.to_s.split('.')
|
|
148
|
-
keys.reduce(obj) { |memo, key| memo.respond_to?(:[]) ? memo[key] : nil }
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
def to_s
|
|
152
|
-
'#<SchemaGraphy::SafeTransform>'
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
private
|
|
156
|
-
|
|
157
|
-
# Handles access to variables in the context.
|
|
158
|
-
def method_missing(name, *args, &block)
|
|
159
|
-
key = name.to_s
|
|
160
|
-
if @context.key?(key) && args.empty? && block.nil?
|
|
161
|
-
@context[key]
|
|
162
|
-
else
|
|
163
|
-
::Kernel.raise ::NoMethodError, "undefined method `#{name}` for #{self}"
|
|
164
|
-
end
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
def respond_to_missing? name, include_private = false
|
|
168
|
-
@context.key?(name.to_s) || super
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
# Disable methods that could be used to break out of the sandbox.
|
|
172
|
-
|
|
173
|
-
def instance_exec(*_args)
|
|
174
|
-
::Kernel.raise ::NoMethodError, 'disabled'
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
def method(*_args)
|
|
178
|
-
::Kernel.raise ::NoMethodError, 'disabled'
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
def singleton_class(*_args)
|
|
182
|
-
::Kernel.raise ::NoMethodError, 'disabled'
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
def define_singleton_method(*_args)
|
|
186
|
-
::Kernel.raise ::NoMethodError, 'disabled'
|
|
187
|
-
end
|
|
188
|
-
end
|
|
189
|
-
end
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module SchemaGraphy
|
|
4
|
-
# A utility module for introspecting schema definitions.
|
|
5
|
-
# Provides methods for retrieving metadata, default values, and type information
|
|
6
|
-
# from a schema hash using a dot-separated path syntax.
|
|
7
|
-
module SchemaUtils
|
|
8
|
-
module_function
|
|
9
|
-
|
|
10
|
-
# Retrieve a nested property definition from a schema using a dot-separated path.
|
|
11
|
-
#
|
|
12
|
-
# @example Schema Structure
|
|
13
|
-
# schema = {
|
|
14
|
-
# "$schema": {
|
|
15
|
-
# "properties": {
|
|
16
|
-
# "property1": {
|
|
17
|
-
# "properties": {
|
|
18
|
-
# "subproperty1": {
|
|
19
|
-
# "default": "value1",
|
|
20
|
-
# "type": "String"
|
|
21
|
-
# }
|
|
22
|
-
# }
|
|
23
|
-
# }
|
|
24
|
-
# }
|
|
25
|
-
# }
|
|
26
|
-
# }
|
|
27
|
-
# crawl_properties(schema, "property1.subproperty1")
|
|
28
|
-
# # => { "default" => "value1", "type" => "String" }
|
|
29
|
-
#
|
|
30
|
-
# @param schema [Hash] The schema hash to crawl.
|
|
31
|
-
# @param path [String] The dot-separated path to the property.
|
|
32
|
-
# @return [Hash, nil] The property definition hash, or `nil` if not found.
|
|
33
|
-
def crawl_properties schema, path
|
|
34
|
-
path_components = path.split('.')
|
|
35
|
-
current = schema['$schema'] || schema
|
|
36
|
-
|
|
37
|
-
path_components.each do |component|
|
|
38
|
-
return nil unless current.is_a?(Hash)
|
|
39
|
-
return nil unless current['properties']&.key?(component)
|
|
40
|
-
|
|
41
|
-
current = current['properties'][component]
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
current
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# Get the default value for a property from the schema.
|
|
48
|
-
#
|
|
49
|
-
# @param schema [Hash] The schema hash.
|
|
50
|
-
# @param path [String] The dot-separated path to the property.
|
|
51
|
-
# @return [Object, nil] The default value, or `nil` if not defined.
|
|
52
|
-
def default_for schema, path
|
|
53
|
-
property = crawl_properties(schema, path)
|
|
54
|
-
return nil unless property.is_a?(Hash)
|
|
55
|
-
|
|
56
|
-
property['default'] || property['dflt']
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
# Get the type for a property from the schema.
|
|
60
|
-
#
|
|
61
|
-
# @param schema [Hash] The schema hash.
|
|
62
|
-
# @param path [String] The dot-separated path to the property.
|
|
63
|
-
# @return [String, nil] The property type, or `nil` if not defined.
|
|
64
|
-
def type_for schema, path
|
|
65
|
-
property = crawl_properties(schema, path)
|
|
66
|
-
return nil unless property.is_a?(Hash)
|
|
67
|
-
|
|
68
|
-
property['type']
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
# Get the templating configuration for a property from the schema.
|
|
72
|
-
#
|
|
73
|
-
# @param schema [Hash] The schema hash.
|
|
74
|
-
# @param path [String] The dot-separated path to the property.
|
|
75
|
-
# @return [Hash] The templating configuration hash.
|
|
76
|
-
def templating_config_for schema, path
|
|
77
|
-
property = crawl_properties(schema, path)
|
|
78
|
-
return {} unless property.is_a?(Hash)
|
|
79
|
-
|
|
80
|
-
return property['templating'] if property['templating']
|
|
81
|
-
|
|
82
|
-
if property['type'].to_s.downcase == 'liquid'
|
|
83
|
-
{ 'default' => 'liquid', 'delay' => true }
|
|
84
|
-
elsif property['type'].to_s.downcase == 'erb'
|
|
85
|
-
{ 'default' => 'erb', 'delay' => true }
|
|
86
|
-
else
|
|
87
|
-
{}
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
# Check if a property is a templated field.
|
|
92
|
-
#
|
|
93
|
-
# @param schema [Hash] The schema hash.
|
|
94
|
-
# @param path [String] The dot-separated path to the property.
|
|
95
|
-
# @return [Boolean] `true` if the field has templating configured, `false` otherwise.
|
|
96
|
-
def templated_field? schema, path
|
|
97
|
-
property = crawl_properties(schema, path)
|
|
98
|
-
return false unless property.is_a?(Hash)
|
|
99
|
-
|
|
100
|
-
property.key?('templating') && property['templating'].is_a?(Hash)
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
# Crawl the schema to find the metadata for a given path.
|
|
104
|
-
#
|
|
105
|
-
# @param schema [Hash] The schema hash.
|
|
106
|
-
# @param path [String, nil] The dot-separated path.
|
|
107
|
-
# @return [Hash] The metadata hash.
|
|
108
|
-
def self.crawl_meta schema, path = nil
|
|
109
|
-
parts = path ? path.split('.') : []
|
|
110
|
-
node = schema['$schema'] || schema
|
|
111
|
-
meta = {}
|
|
112
|
-
|
|
113
|
-
parts.each do |part|
|
|
114
|
-
node = node['properties'][part] if node['properties']&.key?(part)
|
|
115
|
-
break unless node.is_a?(Hash)
|
|
116
|
-
|
|
117
|
-
# Only update meta if this level has it
|
|
118
|
-
meta = node if node['templating']
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
meta['$meta'] || meta['sgyml'] || meta['templating'] || {}
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
|
-
end
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module SchemaGraphy
|
|
4
|
-
# A utility module for working with the custom tag data structure.
|
|
5
|
-
# The structure is a hash with 'value' and '__tag__' keys.
|
|
6
|
-
module TagUtils
|
|
7
|
-
# Extracts the original value from a tagged data structure.
|
|
8
|
-
#
|
|
9
|
-
# @param value [Object] The tagged value (a Hash) or any other value.
|
|
10
|
-
# @return [Object] The original value, or the value itself if not tagged.
|
|
11
|
-
def self.detag value
|
|
12
|
-
value.is_a?(Hash) && value.key?('value') ? value['value'] : value
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
# Retrieves the tag from a tagged data structure.
|
|
16
|
-
#
|
|
17
|
-
# @param value [Object] The tagged value (a Hash) or any other value.
|
|
18
|
-
# @return [String, nil] The tag string, or `nil` if not tagged.
|
|
19
|
-
def self.tag_of value
|
|
20
|
-
value.is_a?(Hash) ? value['__tag__'] : nil
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
# Checks if a value has a specific tag.
|
|
24
|
-
#
|
|
25
|
-
# @param value [Object] The tagged value to check.
|
|
26
|
-
# @param tag [String, Symbol] The tag to check for.
|
|
27
|
-
# @return [Boolean] `true` if the value has the specified tag, `false` otherwise.
|
|
28
|
-
def self.tag? value, tag
|
|
29
|
-
tag_of(value)&.to_s == tag.to_s
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
end
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'tilt'
|
|
4
|
-
require_relative '../sourcerer/templating'
|
|
5
|
-
|
|
6
|
-
# frozen_string_literal: true
|
|
7
|
-
|
|
8
|
-
module SchemaGraphy
|
|
9
|
-
# A module for handling templated fields within a data structure based on a schema or definition.
|
|
10
|
-
# It provides methods for pre-compiling and rendering fields using various template engines.
|
|
11
|
-
module Templating
|
|
12
|
-
extend Sourcerer::Templating
|
|
13
|
-
|
|
14
|
-
# Renders a field if it is a template.
|
|
15
|
-
#
|
|
16
|
-
# @param field [Object] The field to render.
|
|
17
|
-
# @param context [Hash] The context to use for rendering.
|
|
18
|
-
# @return [Object] The rendered field, or the original field if it's not a template.
|
|
19
|
-
def self.resolve_field field, context = {}
|
|
20
|
-
render_field_if_template(field, context)
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
# Recursively pre-compiles templated fields in a data structure based on a schema.
|
|
24
|
-
#
|
|
25
|
-
# @param data [Hash] The data to process.
|
|
26
|
-
# @param schema [Hash] The schema defining which fields are templated.
|
|
27
|
-
# @param base_path [String] The base path for the current data level.
|
|
28
|
-
# @param scope [Hash] The scope to use for compilation.
|
|
29
|
-
def self.precompile_from_schema! data, schema, base_path = '', scope: {}
|
|
30
|
-
return unless data.is_a?(Hash)
|
|
31
|
-
|
|
32
|
-
data.each do |key, value|
|
|
33
|
-
path = [base_path, key].reject(&:empty?).join('.')
|
|
34
|
-
|
|
35
|
-
precompile_from_schema!(value, schema, path, scope: scope) if value.is_a?(Hash)
|
|
36
|
-
|
|
37
|
-
next unless SchemaGraphy::SchemaUtils.templated_field?(schema, path)
|
|
38
|
-
|
|
39
|
-
compile_templated_fields!(
|
|
40
|
-
data: data,
|
|
41
|
-
schema: schema,
|
|
42
|
-
fields: [{ key: key, path: path }],
|
|
43
|
-
scope: scope)
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# An alias for the `Sourcerer::Templating::TemplatedField` class.
|
|
48
|
-
TemplatedField = Sourcerer::Templating::TemplatedField
|
|
49
|
-
|
|
50
|
-
# Compiles templated fields in the data.
|
|
51
|
-
#
|
|
52
|
-
# @param data [Hash] The data containing the fields to compile.
|
|
53
|
-
# @param schema [Hash] The schema definition.
|
|
54
|
-
# @param fields [Array<Hash>] An array of fields to compile, each with a `:key` and `:path`.
|
|
55
|
-
# @param scope [Hash] The scope to use for compilation.
|
|
56
|
-
def self.compile_templated_fields! data:, schema:, fields:, scope: {}
|
|
57
|
-
fields.each do |entry|
|
|
58
|
-
key = entry[:key]
|
|
59
|
-
path = entry[:path]
|
|
60
|
-
val = data[key]
|
|
61
|
-
|
|
62
|
-
next unless val.is_a?(String) || (val.is_a?(Hash) && val['__tag__'] && val['value'])
|
|
63
|
-
|
|
64
|
-
raw = val.is_a?(Hash) ? val['value'] : val
|
|
65
|
-
tagged = val.is_a?(Hash)
|
|
66
|
-
config = SchemaGraphy::SchemaUtils.templating_config_for(schema, path)
|
|
67
|
-
engine = tagged ? val['__tag__'] : (config['default'] || 'liquid')
|
|
68
|
-
|
|
69
|
-
compiled = Sourcerer::Templating::Engines.compile(raw, engine)
|
|
70
|
-
|
|
71
|
-
data[key] = if config['delay']
|
|
72
|
-
Sourcerer::Templating::TemplatedField.new(raw, compiled, engine, tagged, inferred: !tagged)
|
|
73
|
-
else
|
|
74
|
-
Sourcerer::Templating::Engines.render(compiled, engine, scope)
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
# Recursively renders all pre-compiled templated fields in a data structure.
|
|
80
|
-
#
|
|
81
|
-
# @param data [Hash, Array] The data structure to process.
|
|
82
|
-
# @param context [Hash] The context to use for rendering.
|
|
83
|
-
def self.render_all_templated_fields! data, context = {}
|
|
84
|
-
return unless data.is_a?(Hash)
|
|
85
|
-
|
|
86
|
-
data.each do |key, value|
|
|
87
|
-
case value
|
|
88
|
-
when TemplatedField
|
|
89
|
-
data[key] = value.render(context)
|
|
90
|
-
when Hash
|
|
91
|
-
render_all_templated_fields!(value, context)
|
|
92
|
-
when Array
|
|
93
|
-
value.each_with_index do |item, idx|
|
|
94
|
-
if item.is_a?(TemplatedField)
|
|
95
|
-
value[idx] = item.render(context)
|
|
96
|
-
elsif item.is_a?(Hash)
|
|
97
|
-
render_all_templated_fields!(item, context)
|
|
98
|
-
end
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
end
|
data/lib/schemagraphy.rb
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative 'schemagraphy/loader'
|
|
4
|
-
require_relative 'schemagraphy/tag_utils'
|
|
5
|
-
require_relative 'schemagraphy/schema_utils'
|
|
6
|
-
require_relative 'schemagraphy/templating'
|
|
7
|
-
require_relative 'schemagraphy/regexp_utils'
|
|
8
|
-
require_relative 'schemagraphy/cfgyml/doc_builder'
|
|
9
|
-
require_relative 'schemagraphy/data_query/json_pointer'
|
|
10
|
-
require_relative 'schemagraphy/cfgyml/path_reference'
|
|
11
|
-
|
|
12
|
-
# SchemaGraphy is a component for working with schema-driven data structures and extending YAML with robust typing and dynamic directives.
|
|
13
|
-
# It provides utilities for loading, validating, and transforming data based on
|
|
14
|
-
# a schema definition, with a focus on templating and safe expression evaluation.
|
|
15
|
-
# This module is under early development and will be spun off as its own gem after ReleaseHx is generally available.
|
|
16
|
-
module SchemaGraphy
|
|
17
|
-
end
|
data/lib/sourcerer/builder.rb
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
# lib/sourcerer/builder.rb
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
require 'asciidoctor'
|
|
5
|
-
require 'fileutils'
|
|
6
|
-
require_relative '../sourcerer'
|
|
7
|
-
|
|
8
|
-
module Sourcerer
|
|
9
|
-
# A build-time code generator that creates assets such as new data, documentation, and even Ruby files from
|
|
10
|
-
# data extracted from AsciiDoc files, such as attributes and tagged regions.
|
|
11
|
-
module Builder
|
|
12
|
-
# Generates a Ruby file at build-time to be used during the build process.
|
|
13
|
-
#
|
|
14
|
-
# @param generated [Hash] A hash with `:path` and `:module` for the generated file.
|
|
15
|
-
# @param attributes [Array<Hash>] A list of attribute sources to build.
|
|
16
|
-
# @param snippets [Array<Hash>] A list of snippets to build.
|
|
17
|
-
# @param regions [Array<Hash>] A list of tagged regions to build.
|
|
18
|
-
# @param templates [Array<Hash>] A list of templates to build (currently unused).
|
|
19
|
-
# @param render [Array<Hash>] A list of render entries (currently unused).
|
|
20
|
-
# rubocop:disable Lint/UnusedMethodArgument
|
|
21
|
-
def self.generate_prebuild generated: {}, attributes: [], snippets: [], regions: [], templates: [], render: []
|
|
22
|
-
# rubocop:enable Lint/UnusedMethodArgument
|
|
23
|
-
# NOTE: templates/render parameters are accepted from config but handled separately by Sourcerer.render_outputs
|
|
24
|
-
attr_result = build_attributes(attributes)
|
|
25
|
-
snippet_lookup = build_outputs(snippets, type: :snippet)
|
|
26
|
-
region_lookup = build_outputs(regions, type: :region)
|
|
27
|
-
|
|
28
|
-
File.write(generated[:path].to_s, <<~RUBY)
|
|
29
|
-
# frozen_string_literal: true
|
|
30
|
-
# Auto-generated by Sourcerer::Builder
|
|
31
|
-
|
|
32
|
-
module #{generated[:module]}
|
|
33
|
-
ATTRIBUTES = #{attr_result.inspect}
|
|
34
|
-
|
|
35
|
-
SNIPPET_LOOKUP = #{snippet_lookup.inspect}
|
|
36
|
-
|
|
37
|
-
REGION_LOOKUP = #{region_lookup.inspect}
|
|
38
|
-
|
|
39
|
-
def self.read_built_snippet name
|
|
40
|
-
fname = SNIPPET_LOOKUP[name.to_s] || name.to_s
|
|
41
|
-
path = File.expand_path("../../../build/snippets/\#{fname}", __FILE__)
|
|
42
|
-
raise "Snippet not found: \#{name}" unless File.exist?(path)
|
|
43
|
-
File.read(path)
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
RUBY
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
# @api private
|
|
50
|
-
# Builds a hash of attributes from the given sources.
|
|
51
|
-
# @param attributes [Array<Hash>] The attribute sources.
|
|
52
|
-
# @return [Hash] The built attributes.
|
|
53
|
-
def self.build_attributes attributes
|
|
54
|
-
attributes.each_with_object({}) do |entry, acc|
|
|
55
|
-
source = entry[:source]
|
|
56
|
-
name = entry[:name] || File.basename(source, '.adoc').to_sym
|
|
57
|
-
acc[name.to_sym] = Sourcerer.load_attributes(source)
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
# @api private
|
|
62
|
-
# Builds output files from snippets or regions and returns a lookup hash.
|
|
63
|
-
# @param entries [Array<Hash>] The entries to build.
|
|
64
|
-
# @param type [Symbol] The type of output (`:snippet` or `:region`).
|
|
65
|
-
# @return [Hash] A lookup hash mapping names to output filenames.
|
|
66
|
-
def self.build_outputs entries, type:
|
|
67
|
-
lookup = {}
|
|
68
|
-
names = []
|
|
69
|
-
outnames = []
|
|
70
|
-
|
|
71
|
-
entries.each do |entry|
|
|
72
|
-
source = entry[:source] or raise ArgumentError, "#{type} entry is missing :source"
|
|
73
|
-
tag = entry[:tag]
|
|
74
|
-
tags = entry[:tags]
|
|
75
|
-
|
|
76
|
-
raise ArgumentError, 'use only one of :tag or :tags' if tag && tags
|
|
77
|
-
raise ArgumentError, "#{type} must include a :tag or :tags" unless tag || tags
|
|
78
|
-
|
|
79
|
-
name = entry[:name] || tag || File.basename(source, '.adoc')
|
|
80
|
-
outname = entry[:out] || default_output_name(name, type)
|
|
81
|
-
|
|
82
|
-
raise ArgumentError, "name value must be unique; #{name} already used" if names.include? name
|
|
83
|
-
raise ArgumentError, "out value must be unique; #{outname} already used" if outnames.include? outname
|
|
84
|
-
|
|
85
|
-
names << name
|
|
86
|
-
outnames << outname
|
|
87
|
-
|
|
88
|
-
tags = [tag] if tag
|
|
89
|
-
|
|
90
|
-
text =
|
|
91
|
-
case type
|
|
92
|
-
when :snippet then Sourcerer.load_include(source, tags: tags)
|
|
93
|
-
when :region then Sourcerer.extract_tagged_content(source, tags: tags)
|
|
94
|
-
else raise ArgumentError, "Unsupported type: #{type}"
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
lookup[name.to_s] = outname
|
|
98
|
-
|
|
99
|
-
outpath = File.join("build/#{type}s", outname)
|
|
100
|
-
FileUtils.mkdir_p File.dirname(outpath)
|
|
101
|
-
File.write(outpath, text)
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
lookup
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
# @api private
|
|
108
|
-
# Determines the default output filename for a given name and type.
|
|
109
|
-
# @param name [String] The name of the output.
|
|
110
|
-
# @param type [Symbol] The type of output.
|
|
111
|
-
# @return [String] The default filename.
|
|
112
|
-
def self.default_output_name name, type
|
|
113
|
-
case type
|
|
114
|
-
when :snippet then "#{name}.txt"
|
|
115
|
-
when :region then "#{name}.adoc"
|
|
116
|
-
else raise ArgumentError, "Unknown type: #{type}"
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
|
-
end
|
|
120
|
-
end
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'jekyll'
|
|
4
|
-
require 'jekyll-asciidoc'
|
|
5
|
-
|
|
6
|
-
module Sourcerer
|
|
7
|
-
module Jekyll
|
|
8
|
-
# This module provides methods for programmatically setting up a Jekyll site
|
|
9
|
-
# environment, which is useful for loading plugins or creating a mock site for rendering.
|
|
10
|
-
module Bootstrapper
|
|
11
|
-
# Loads Jekyll plugins from specified directories.
|
|
12
|
-
#
|
|
13
|
-
# @param plugin_dirs [Array<String>] A list of directories to search for plugins.
|
|
14
|
-
# @return [Jekyll::Site] The initialized Jekyll site object.
|
|
15
|
-
def self.load_plugins plugin_dirs: []
|
|
16
|
-
config = ::Jekyll.configuration(
|
|
17
|
-
{
|
|
18
|
-
'source' => Dir.pwd,
|
|
19
|
-
'destination' => File.join(Dir.pwd, '_site'),
|
|
20
|
-
'quiet' => true,
|
|
21
|
-
'skip_config_files' => true,
|
|
22
|
-
'plugins_dir' => plugin_dirs.map { |d| File.expand_path(d) },
|
|
23
|
-
'disable_disk_cache' => true
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
site = ::Jekyll::Site.new(config)
|
|
27
|
-
site.plugin_manager.conscientious_require
|
|
28
|
-
|
|
29
|
-
::Jekyll::Hooks.trigger :site, :after_init, site
|
|
30
|
-
|
|
31
|
-
site
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
# Creates an ephemeral Jekyll site instance for rendering purposes.
|
|
35
|
-
# This is useful for leveraging Jekyll's templating outside of a full site build.
|
|
36
|
-
#
|
|
37
|
-
# @param includes_load_paths [Array<String>] Paths to load includes from.
|
|
38
|
-
# @param plugin_dirs [Array<String>] Paths to load plugins from.
|
|
39
|
-
# @return [Jekyll::Site] The initialized fake Jekyll site object.
|
|
40
|
-
# rubocop:disable Lint/UnusedMethodArgument
|
|
41
|
-
def self.fake_site includes_load_paths: [], plugin_dirs: []
|
|
42
|
-
# NOTE: plugin_dirs parameter is accepted but not yet implemented; reserved for future plugin loading
|
|
43
|
-
::Jekyll.logger.log_level = :error if ::Jekyll.logger.respond_to?(:log_level=)
|
|
44
|
-
|
|
45
|
-
config = ::Jekyll.configuration(
|
|
46
|
-
'source' => Dir.pwd,
|
|
47
|
-
'includes_dir' => includes_load_paths.first,
|
|
48
|
-
'includes_load_paths' => includes_load_paths,
|
|
49
|
-
'destination' => File.join(Dir.pwd, '_site'),
|
|
50
|
-
'quiet' => true,
|
|
51
|
-
'skip_config_files' => true,
|
|
52
|
-
'disable_disk_cache' => true)
|
|
53
|
-
|
|
54
|
-
site = ::Jekyll::Site.new(config)
|
|
55
|
-
|
|
56
|
-
include_paths = site.includes_load_paths || []
|
|
57
|
-
site.inclusions ||= {}
|
|
58
|
-
|
|
59
|
-
include_paths.each do |dir|
|
|
60
|
-
Dir[File.join(dir, '**/*')].each do |file|
|
|
61
|
-
next unless File.file?(file)
|
|
62
|
-
|
|
63
|
-
relative_path = file.sub("#{dir}/", '')
|
|
64
|
-
site.inclusions[relative_path] = File.read(file)
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
site.instance_variable_set(:@liquid_renderer, ::Jekyll::LiquidRenderer.new(site))
|
|
69
|
-
|
|
70
|
-
plugin_manager = ::Jekyll::PluginManager.new(site)
|
|
71
|
-
plugin_manager.conscientious_require
|
|
72
|
-
|
|
73
|
-
site
|
|
74
|
-
end
|
|
75
|
-
# rubocop:enable Lint/UnusedMethodArgument
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
end
|