schemagraphy 0.1.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 +7 -0
- data/README.adoc +260 -0
- data/lib/schemagraphy/attribute_resolver.rb +48 -0
- data/lib/schemagraphy/cfgyml/definition.rb +93 -0
- data/lib/schemagraphy/cfgyml/doc_builder.rb +52 -0
- data/lib/schemagraphy/cfgyml/path_reference.rb +24 -0
- data/lib/schemagraphy/cfgyml/templates/config-property.adoc.liquid +57 -0
- data/lib/schemagraphy/cfgyml/templates/config-reference.adoc.liquid +33 -0
- data/lib/schemagraphy/cfgyml/templates/sample-config.yaml.liquid +46 -0
- data/lib/schemagraphy/cfgyml/templates/sample-property.yaml.liquid +82 -0
- data/lib/schemagraphy/data_query/json_pointer.rb +42 -0
- data/lib/schemagraphy/liquid/filters.rb +22 -0
- data/lib/schemagraphy/loader.rb +59 -0
- data/lib/schemagraphy/regexp_utils.rb +235 -0
- data/lib/schemagraphy/safe_expression.rb +189 -0
- data/lib/schemagraphy/schema_utils.rb +124 -0
- data/lib/schemagraphy/sgyml.rb +59 -0
- data/lib/schemagraphy/tag_utils.rb +32 -0
- data/lib/schemagraphy/templating.rb +104 -0
- data/lib/schemagraphy/version.rb +5 -0
- data/lib/schemagraphy.rb +17 -0
- metadata +147 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: af25a161a4a5cb922cb4686ab1e646c6b956a7b5d51b38501f6aba4f4d2a3f7c
|
|
4
|
+
data.tar.gz: 144e3abb6f698a525f77b10e01ccd9845e05d8b7ea42ccfcf42694e6de7c7aa9
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 01b5f00420d26307215a11fc6e07b14ee3b6ae32b44a144bdf592aef741c27006e7970f7f8762a31e8356fd4cf8c89d8d7d641bfcedbd77898c81d51e7e41e5a
|
|
7
|
+
data.tar.gz: 8456db9732c0a1f14aed8d363e095331f235667b83048d17e718cd95146ba9dcc5a0dbdeaaa0b06ef594464d187bbb1d0e51c64af4d1379b19ee6807b81039fc
|
data/README.adoc
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
= SchemaGraphy
|
|
2
|
+
:toc: macro
|
|
3
|
+
:toclevels: 3
|
|
4
|
+
// tag::ai-prompt[]
|
|
5
|
+
// tag::global-settings[]
|
|
6
|
+
:this_proj_slug: schemagraphy
|
|
7
|
+
:this_proj_name: {doctitle}
|
|
8
|
+
// tag::universal-settings[]
|
|
9
|
+
// ALL changes within this block must be made in prime template:
|
|
10
|
+
// DocOps/lab/gems/docopslab-dev/templates/README.asciidoc
|
|
11
|
+
:docopslab_src_www_url: https://github.com/DocOps
|
|
12
|
+
:docopslab_domain: docopslab.org
|
|
13
|
+
:docopslab_www_url: https://{docopslab_domain}
|
|
14
|
+
:docopslab_io_www_url: https://docopslab.github.io
|
|
15
|
+
:docopslab_ruby_version: 3.2.7
|
|
16
|
+
:docopslab_git_src_uri: git@github.com:DocOps
|
|
17
|
+
:docopslab_src_raw_url: https://raw.githubusercontent.com/DocOps
|
|
18
|
+
:this_proj_src_www_url: {docopslab_src_www_url}/{this_proj_slug}
|
|
19
|
+
:this_proj_src_raw_url: {docopslab_src_raw_url}/{this_proj_slug}
|
|
20
|
+
:this_proj_src_main_raw_url: {this_proj_src_raw_url}/main
|
|
21
|
+
:this_proj_src_main_files_url: {this_proj_src_www_url}/blob/main
|
|
22
|
+
:this_proj_src_git_uri: {docopslab_git_src_uri}/{this_proj_slug}.git
|
|
23
|
+
:this_proj_ruby_version: {docopslab_ruby_version}
|
|
24
|
+
// tag::env-settings[]
|
|
25
|
+
:docs_extn:
|
|
26
|
+
ifdef::env-github[]
|
|
27
|
+
:docs_extn: .adoc
|
|
28
|
+
:icons: font
|
|
29
|
+
:caution-caption: :fire:
|
|
30
|
+
:important-caption: :exclamation:
|
|
31
|
+
:note-caption: :paperclip:
|
|
32
|
+
:tip-caption: :bulb:
|
|
33
|
+
:warning-caption: :warning:
|
|
34
|
+
endif::[]
|
|
35
|
+
// end::env-settings[]
|
|
36
|
+
// Settings likely to be overridden locally
|
|
37
|
+
:this_prod_slug: {this_proj_slug}
|
|
38
|
+
:this_prod_name: {this_proj_name}
|
|
39
|
+
:this_prod_src_www_url: {this_proj_src_www_url}
|
|
40
|
+
// end::universal-settings[]
|
|
41
|
+
// tag::product-settings[]
|
|
42
|
+
// tag::version-settings[]
|
|
43
|
+
:this_prod_ruby_version_prime: {docopslab_ruby_version_prime}
|
|
44
|
+
:this_prod_ruby_version_range: {docopslab_ruby_version_range}
|
|
45
|
+
:this_prod_vrsn_major: 0
|
|
46
|
+
:this_prod_vrsn_minor: 1
|
|
47
|
+
:this_prod_vrsn_majmin: {this_prod_vrsn_major}.{this_prod_vrsn_minor}
|
|
48
|
+
:this_prod_vrsn_patch: 0
|
|
49
|
+
:this_prod_vrsn_suffix:
|
|
50
|
+
:this_prod_vrsn: {this_prod_vrsn_majmin}.{this_prod_vrsn_patch}{this_prod_vrsn_suffix}
|
|
51
|
+
:next_prod_vrsn_majmin: 0.2
|
|
52
|
+
:next_prod_vrsn_patch: 0
|
|
53
|
+
:next_prod_vrsn: {next_prod_vrsn_majmin}.{next_prod_vrsn_patch}
|
|
54
|
+
// end::version-settings[]
|
|
55
|
+
:this_prod_www_url: https://{this_proj_slug}.{docopslab_domain}
|
|
56
|
+
:this_prod_docs_url: {this_prod_www_url}/docs
|
|
57
|
+
:gem_config_definition_path: ./specs/data/config-def.yml
|
|
58
|
+
:app_default_config_path: ./.{this_prod_slug}.yml
|
|
59
|
+
:docker_run_command: docker run -it --rm --user $(id -u):$(id -g) -v $(pwd):/workdir docopslab/{this_prod_slug} {this_prod_slug}
|
|
60
|
+
// end::product-settings[]
|
|
61
|
+
// end::global-settings[]
|
|
62
|
+
// end::ai-prompt[]
|
|
63
|
+
|
|
64
|
+
*SchemaGraphy* is an aspirational framework for governing data and technical content in flat-file formats.
|
|
65
|
+
It is based in a modestly extended form of YAML syntax and preprocessing, and is designed to be used in a variety of contexts.
|
|
66
|
+
|
|
67
|
+
For now, the _released and supported_ version of this project is a Ruby gem that provides a simple API for processing and validating specially formatted YAML files, mainly for generating single-sourced documentation from them.
|
|
68
|
+
This gem is being released to support divergent downstream projects (so far: ReleaseHx and DocOps Lab's Jekyll extensions suite).
|
|
69
|
+
|
|
70
|
+
SchemaGraphy is basically an extension of YAML, enabling Ruby developers and end users more broadly to powerfully interpret and schematize YAML-based data.
|
|
71
|
+
Most relevant to our case, as enabled by the `SchemaGraphy` module in this gem, is its handling of *YAML custom tags*, *attribute resolution*, and what I am calling *"`templated fields`"*, where the value of a YAML node is a String that is intended to be further processed by a templating engine like Liquid or ERB, either immediately upon ingest or later in the runtime stack, when it can be mixed with additional data.
|
|
72
|
+
|
|
73
|
+
SchemaGraphy facilitates handling these and other quirky power-ups we use with our fully valid YAML files, so low-code users can pass some dynamism along in their YAML configs and so forth.
|
|
74
|
+
|
|
75
|
+
API reference documentation is published at link:https://rubydoc.info/gems/schemagraphy[rubydoc.info/gems/schemagraphy].
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
[[attribute-resolution]]
|
|
79
|
+
== Attribute Resolution
|
|
80
|
+
|
|
81
|
+
SchemaGraphy provides *attribute resolution* capabilities through the `AttributeResolver` component.
|
|
82
|
+
This enables YAML files to reference external attributes using placeholder syntax like `{attribute_name}`.
|
|
83
|
+
|
|
84
|
+
When loading YAML with `.load_yaml_with_attributes(file_path, attributes)`, SchemaGraphy:
|
|
85
|
+
|
|
86
|
+
. Loads the YAML file normally
|
|
87
|
+
. Searches for `{attribute_name}` patterns in String values
|
|
88
|
+
. Replaces them with corresponding values from the provided attributes Hash
|
|
89
|
+
. Returns the resolved YAML data structure
|
|
90
|
+
|
|
91
|
+
This is used extensively in ReleaseHx's configuration system to enable single-sourcing of defaults from README attributes.
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
[[custom-yaml-tag-handling]]
|
|
95
|
+
== Custom YAML Tag Handling
|
|
96
|
+
|
|
97
|
+
To enable end users to pass meta-instructions along with their data, wherever it will make sense to do so, SchemaGraphy offers a straightforward handling system.
|
|
98
|
+
|
|
99
|
+
Wherever you parse YAML-formatted data using `.load_yaml_with_tags`, custom-tagged Scalar nodes are converted into Maps like so:
|
|
100
|
+
|
|
101
|
+
.User's YAML
|
|
102
|
+
[source,yaml]
|
|
103
|
+
----
|
|
104
|
+
some_property: !liquid "{{ some_value }}"
|
|
105
|
+
----
|
|
106
|
+
|
|
107
|
+
.Converted data TagMap
|
|
108
|
+
[source,yaml]
|
|
109
|
+
----
|
|
110
|
+
some_property:
|
|
111
|
+
__tag__: liquid
|
|
112
|
+
value: "{{ some_value }}"
|
|
113
|
+
----
|
|
114
|
+
|
|
115
|
+
Developers may therefore conditionally interpret ingested data based on user-defined classifications, wherever the developer supports such things.
|
|
116
|
+
|
|
117
|
+
Whether a Scalar has been transformed into a TagMap, you can resolve it using:
|
|
118
|
+
|
|
119
|
+
[source,ruby]
|
|
120
|
+
----
|
|
121
|
+
SchemaGraphy::TagUtils.detag(some_property)
|
|
122
|
+
# Or, with a local alias
|
|
123
|
+
detag = ->(val) { SchemaGraphy::TagUtils.detag(val) }
|
|
124
|
+
detag(some_property)
|
|
125
|
+
----
|
|
126
|
+
|
|
127
|
+
When tags are used this way, to convey a syntax/engine for processing a template or other dynamic content, SchemaGraphy can even help us handle the content in the manner designated by the tag.
|
|
128
|
+
This will come up again in <<templated-fields,the next section>>.
|
|
129
|
+
|
|
130
|
+
[NOTE]
|
|
131
|
+
This capability is only available on Scalar node values.
|
|
132
|
+
For now, tags applied to other compound node types (Arrays/sequences, Maps/mappings) will be ignored by SchemaGraphy interpreters.
|
|
133
|
+
|
|
134
|
+
[WARNING]
|
|
135
|
+
When you use `load_yaml_with_tags`, you will encounter errors downstream if a user places a tag on a node where you do not expect it.
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
[[templated-fields]]
|
|
139
|
+
== Templated Property Values in YAML
|
|
140
|
+
|
|
141
|
+
We are calling these "`templated fields`" to specify that we are talking about enabling end users to use Liquid, ERB, or eventually other templating syntaxes in YAML node values.
|
|
142
|
+
|
|
143
|
+
In so doing, developer are able to designate that the value of certain YAML nodes should be handled by a templating engine, as well as when and how.
|
|
144
|
+
|
|
145
|
+
We'll look at how this is done in <<templated-fields-handling>>.
|
|
146
|
+
For now, the point is that sometimes files like `specs/data/config-def.yml` or an API-mapping file call for a little more runtime dynamism than a low-code solution like pure YAML can support.
|
|
147
|
+
|
|
148
|
+
Therefore, when the value of a user-configurable or environment-deterimined "`setting`" is a string that must be generated from data defined outside that field, we parse and render the template at runtime, using data from the environment or elsewhere.
|
|
149
|
+
For now, it is up to our calling code to provide the appropriate variables to the template depending on the context.
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
[[config-def]]
|
|
153
|
+
== Configuration Definition (CFGYML)
|
|
154
|
+
|
|
155
|
+
All user-configurable settings have a definition, if not also a default value.
|
|
156
|
+
For single-sourcing purposes, these are defined in a YAML format called CFGYML -- a configuration-file modeling language.
|
|
157
|
+
|
|
158
|
+
The file is at `{gem_config_definition_path}`.
|
|
159
|
+
It is used to establish the literal default settings carried by the application, and also to document those settings for the user.
|
|
160
|
+
|
|
161
|
+
This practice lets developers give end users extremely detailed configurations, always well documented.
|
|
162
|
+
|
|
163
|
+
[[attribute-resolution-in-cfgyml]]
|
|
164
|
+
=== Attribute Resolution in CFGYML
|
|
165
|
+
|
|
166
|
+
CFGYML supports *attribute resolution* from AsciiDoc files (like this README) using placeholder syntax.
|
|
167
|
+
Default values can reference README attributes using `{attribute_name}` syntax:
|
|
168
|
+
|
|
169
|
+
[source,yaml]
|
|
170
|
+
----
|
|
171
|
+
properties:
|
|
172
|
+
$meta:
|
|
173
|
+
properties:
|
|
174
|
+
markup:
|
|
175
|
+
dflt: "{default_markup}" # Resolved from README.adoc :default_markup: attribute
|
|
176
|
+
----
|
|
177
|
+
|
|
178
|
+
This enables single-sourcing of configuration defaults from README attributes, ensuring that documentation and defaults stay synchronized.
|
|
179
|
+
|
|
180
|
+
[[cfgyml-schema-structure]]
|
|
181
|
+
=== CFGYML Schema Structure
|
|
182
|
+
|
|
183
|
+
The basic schema is somewhat straightforward.
|
|
184
|
+
Essentially, you're nesting Map objects within a YAML key `properties`, and each property (setting) of the defined config file can be described and constrained.
|
|
185
|
+
|
|
186
|
+
Each setting can have a type, description (`desc`), default (`dflt`), and templated-field instructions (`templating`).
|
|
187
|
+
If the setting is itself a of type `Map` (YAML "`mapping`", JSON "`object`"), its own nested parameters can be established with a `properties:` block.
|
|
188
|
+
|
|
189
|
+
For now, you can designate the type, which you will have to enforce in your code, as well as a default value.
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
[[sgyml-schemas]]
|
|
193
|
+
== SGYML Schemas
|
|
194
|
+
|
|
195
|
+
Similar to but more complicated than CFGYML definition files are SchemaGraphy schema files.
|
|
196
|
+
This is a partially specified, partially developed, and as-yet-incomplete syntax for designating and constraining YAML documents.
|
|
197
|
+
|
|
198
|
+
ReleaseHx and CFGYML at this time make active use of only minimal aspects of these schemas.
|
|
199
|
+
|
|
200
|
+
Each of the YAML formats used by ReleaseHx has its own schema in the repo.
|
|
201
|
+
The cfgyml-schema.yaml file will eventually be spun off, but the `specs/data/rhyml-schema.yaml` and `specs/data/rhyml-mapping-schema.yaml` files will stay here, defining valid formats for the types of files they apply to.
|
|
202
|
+
|
|
203
|
+
Since SchemaGraphy itself is still unreleased, CFGYML as introduced in this gem offers only a subset of what it will enable down the road.
|
|
204
|
+
|
|
205
|
+
Once SchemaGraphy is generally available, this gem will call it as a dependency.
|
|
206
|
+
At that point, a file like `specs/data/config-def.yml` (CFGYML) will be able to impose a more detailed `$schema` for any property.
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
[[templated-fields-handling]]
|
|
210
|
+
== Dynamic Templated-field Handling
|
|
211
|
+
|
|
212
|
+
The most powerful function of SchemaGraphy schemas that is now available in ReleaseHx is the ability to instruct how templated fields should be processed at different stages, and also to parse and render them as needed.
|
|
213
|
+
|
|
214
|
+
Templated-field handling can be established between a combination of (1) CFGYML definition files or SGYML schema files and (2) configuration files to be applied at runtime.
|
|
215
|
+
|
|
216
|
+
Developers can designate a given property to be `type: Template` in a schema or definition.
|
|
217
|
+
This "`typing`" can be a trigger for downstream parsing/rendering of the template.
|
|
218
|
+
|
|
219
|
+
[NOTE]
|
|
220
|
+
Liquid uses these two stages.
|
|
221
|
+
The _parse_ operation compiles a template into a `Liquid::Template` object.
|
|
222
|
+
The _render_ operation applies a dataset to the loaded template, generating a String with Liquid tags resolved.
|
|
223
|
+
|
|
224
|
+
[[nyi]]
|
|
225
|
+
== Not Yet Implemented
|
|
226
|
+
|
|
227
|
+
Most aspects of SchemaGraphy/SGYML are planned but not yet available, but some are worth pointing out.
|
|
228
|
+
|
|
229
|
+
data types::
|
|
230
|
+
As of now, the `type` node of any property in `specs/data/config-def.yml` is not particularly functional.
|
|
231
|
+
I do have a whole table of "`data types`" in SGYML, most of which are extremely self-explanatory and drawn from fairly platonic, cross-language terms.
|
|
232
|
+
+
|
|
233
|
+
However, these are entirely unenforced in ReleaseHx -- for now, data still has to be type checked explicitly in the Ruby code, and user configs are not validated against any kind of typing system.
|
|
234
|
+
|
|
235
|
+
schema docs::
|
|
236
|
+
The schema files do not yet generate complete reference docs for the subject files that they govern.
|
|
237
|
+
So for instance, you'll have to read files like `specs/data/rhyml-schema.yaml` and `specs/data/rhyml-mapping-schema.yaml` directly to understand the format of RHYML files and how they are mapped to REST response payloads.
|
|
238
|
+
|
|
239
|
+
SGYML validation and resolvers::
|
|
240
|
+
The SGYML schemas are not yet being used to validate YAML files or to dereference `$ref` nodes and other enhancements.
|
|
241
|
+
|
|
242
|
+
URIx specification::
|
|
243
|
+
A lightweight standard for designating and parsing extended URIs to enable identification of structured data/text in flat files at any address.
|
|
244
|
+
|
|
245
|
+
[[future]]
|
|
246
|
+
=== The Future of SchemaGraphy
|
|
247
|
+
|
|
248
|
+
// tag::features[]
|
|
249
|
+
* Add [.buzz]*transclusion* (`$ref`) capability to YAML documents
|
|
250
|
+
* Govern [.buzz]*serialized, nested data* objects, including default properties and _inter-object relationships_.
|
|
251
|
+
* Constrain [.buzz]*AsciiDoc*, [.buzz]*Markdown*, [.buzz]*restructuredText*, and any other lightweight markup _text formatting_ to meet your custom standards.
|
|
252
|
+
* Generate [.buzz]*linter definitions* based on your schemas.
|
|
253
|
+
* Automatically [.buzz]*generate documentation* for APIs, config files, and marked-up datasets.
|
|
254
|
+
* Even [.buzz.nokey]*validate HTML and rich-test* output of your source markup!
|
|
255
|
+
* Port to and from third-party schema models like [.buzz]*JSON Schema** and [.buzz]*GraphQL*.
|
|
256
|
+
* [.buzz.nokey]*Render HTML forms* for data collection with one click.
|
|
257
|
+
* Ingest [.buzz.nokey]*default values* from a config file definition.
|
|
258
|
+
* [.buzz.nokey]*Generate stubs/drafts* data and documents.
|
|
259
|
+
* All with [.buzz.nokey]*human-friendly YAML* files and objects, with AsciiDoc supported internally...
|
|
260
|
+
// end::features[]
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SchemaGraphy
|
|
4
|
+
# The AttributeResolver module provides methods for resolving AsciiDoc attribute references
|
|
5
|
+
# within a schema hash. It is used to substitute placeholders like `\{attribute_name}`
|
|
6
|
+
# with actual values.
|
|
7
|
+
module AttributeResolver
|
|
8
|
+
# Recursively walk a schema Hash and resolve `\{attribute_name}` references
|
|
9
|
+
# in 'dflt' values.
|
|
10
|
+
#
|
|
11
|
+
# @param schema [Hash] The schema or definition hash to process.
|
|
12
|
+
# @param attrs [Hash] The key-value pairs from AsciiDoc attributes to use for resolution.
|
|
13
|
+
# @return [Hash] The schema with resolved attributes.
|
|
14
|
+
def self.resolve_attributes! schema, attrs
|
|
15
|
+
case schema
|
|
16
|
+
when Hash
|
|
17
|
+
schema.transform_values! do |value|
|
|
18
|
+
if value.is_a?(Hash)
|
|
19
|
+
if value.key?('dflt') && value['dflt'].is_a?(String)
|
|
20
|
+
value['dflt'] = resolve_attribute_reference(value['dflt'], attrs)
|
|
21
|
+
end
|
|
22
|
+
resolve_attributes!(value, attrs)
|
|
23
|
+
else
|
|
24
|
+
value
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
schema
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Replace `\{attribute_name}` patterns with corresponding values from the attrs hash.
|
|
32
|
+
#
|
|
33
|
+
# @param value [String] The string to process.
|
|
34
|
+
# @param attrs [Hash] The attributes to use for resolution.
|
|
35
|
+
# @return [String] The processed string with attribute references replaced.
|
|
36
|
+
def self.resolve_attribute_reference value, attrs
|
|
37
|
+
# Handle \{attribute_name} references
|
|
38
|
+
if value.match?(/\{[^}]+\}/)
|
|
39
|
+
value.gsub(/\{([^}]+)\}/) do |match|
|
|
40
|
+
attr_name = ::Regexp.last_match(1)
|
|
41
|
+
attrs[attr_name] || match # Keep original if no matching attribute
|
|
42
|
+
end
|
|
43
|
+
else
|
|
44
|
+
value
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../loader'
|
|
4
|
+
require_relative '../schema_utils'
|
|
5
|
+
|
|
6
|
+
module SchemaGraphy
|
|
7
|
+
# A module for handling CFGYML, a schema-driven configuration system.
|
|
8
|
+
module CFGYML
|
|
9
|
+
# Represents a configuration definition loaded from a schema file.
|
|
10
|
+
# It provides methods for accessing defaults and rendering documentation.
|
|
11
|
+
class Definition
|
|
12
|
+
# @return [Hash] The loaded schema hash.
|
|
13
|
+
attr_reader :schema
|
|
14
|
+
|
|
15
|
+
# @return [Hash] The attributes used for resolving placeholders in the schema.
|
|
16
|
+
attr_reader :attributes
|
|
17
|
+
|
|
18
|
+
# @param schema_path [String] The path to the schema YAML file.
|
|
19
|
+
# @param attrs [Hash] A hash of attributes for placeholder resolution.
|
|
20
|
+
def initialize schema_path, attrs = {}
|
|
21
|
+
@schema = Loader.load_yaml_with_attributes(schema_path, attrs)
|
|
22
|
+
@attributes = attrs
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Extract default values from the loaded schema.
|
|
26
|
+
# @return [Hash] A hash of default values.
|
|
27
|
+
# @note This method is a placeholder. SchemaUtils.crawl_defaults/1 is not yet implemented.
|
|
28
|
+
def defaults
|
|
29
|
+
# TODO: Implement crawl_defaults in SchemaUtils
|
|
30
|
+
# For now, return an empty hash
|
|
31
|
+
{}
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Get the search paths for templates.
|
|
35
|
+
# @return [Array<String>] An array of template paths.
|
|
36
|
+
def template_paths
|
|
37
|
+
@template_paths ||= [
|
|
38
|
+
File.join(File.dirname(__FILE__), 'templates'),
|
|
39
|
+
*additional_template_paths
|
|
40
|
+
]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Render a configuration reference or sample in the specified format.
|
|
44
|
+
#
|
|
45
|
+
# @param format [Symbol] The output format (`:adoc` or `:yaml`).
|
|
46
|
+
# @return [String] The rendered output.
|
|
47
|
+
# @raise [ArgumentError] if the format is unsupported.
|
|
48
|
+
def render_reference format = :adoc
|
|
49
|
+
template = case format
|
|
50
|
+
when :adoc
|
|
51
|
+
'config-reference.adoc.liquid'
|
|
52
|
+
when :yaml
|
|
53
|
+
'sample-config.yaml.liquid'
|
|
54
|
+
else
|
|
55
|
+
raise ArgumentError, "Unsupported format: #{format}"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
render_template(template)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
# Render a template using the Liquid engine.
|
|
64
|
+
def render_template template_name
|
|
65
|
+
template_path = find_template(template_name)
|
|
66
|
+
raise "Template not found: #{template_name}" unless template_path
|
|
67
|
+
|
|
68
|
+
require 'liquid'
|
|
69
|
+
template_content = File.read(template_path)
|
|
70
|
+
template = Liquid::Template.parse(template_content)
|
|
71
|
+
|
|
72
|
+
template.render(
|
|
73
|
+
'config_def' => @schema,
|
|
74
|
+
'attrs' => @attributes)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Find a template file in the configured template paths.
|
|
78
|
+
def find_template name
|
|
79
|
+
template_paths.each do |path|
|
|
80
|
+
file = File.join(path, name)
|
|
81
|
+
return file if File.exist?(file)
|
|
82
|
+
end
|
|
83
|
+
nil
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Provides an extension point for subclasses to add more template paths.
|
|
87
|
+
def additional_template_paths
|
|
88
|
+
# Can be overridden by subclasses
|
|
89
|
+
[]
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module SchemaGraphy
|
|
6
|
+
module CFGYML
|
|
7
|
+
# Builds documentation-friendly CFGYML references for machine consumption.
|
|
8
|
+
module DocBuilder
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
def call schema, options = {}
|
|
12
|
+
pretty = options.fetch(:pretty, true)
|
|
13
|
+
data = reference_hash(schema)
|
|
14
|
+
pretty ? JSON.pretty_generate(data) : JSON.generate(data)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def reference_hash schema
|
|
18
|
+
{
|
|
19
|
+
'format' => 'releasehx-config-reference',
|
|
20
|
+
'version' => 1,
|
|
21
|
+
'properties' => build_properties(schema['properties'], [])
|
|
22
|
+
}
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def build_properties properties, path
|
|
26
|
+
return {} unless properties.is_a?(Hash)
|
|
27
|
+
|
|
28
|
+
properties.each_with_object({}) do |(key, definition), acc|
|
|
29
|
+
next unless definition.is_a?(Hash)
|
|
30
|
+
|
|
31
|
+
current_path = path + [key]
|
|
32
|
+
entry = build_entry(current_path, definition)
|
|
33
|
+
children = build_properties(definition['properties'], current_path)
|
|
34
|
+
entry['properties'] = children unless children.empty?
|
|
35
|
+
acc[key] = entry
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def build_entry path, definition
|
|
40
|
+
entry = {
|
|
41
|
+
'path' => path.join('.'),
|
|
42
|
+
'desc' => definition['desc'],
|
|
43
|
+
'docs' => definition['docs'],
|
|
44
|
+
'type' => definition['type'],
|
|
45
|
+
'templating' => definition['templating'],
|
|
46
|
+
'default' => definition.key?('dflt') ? definition['dflt'] : nil
|
|
47
|
+
}
|
|
48
|
+
entry.compact
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module SchemaGraphy
|
|
6
|
+
module CFGYML
|
|
7
|
+
# Loads and queries a JSON config reference using JSON Pointer.
|
|
8
|
+
class PathReference
|
|
9
|
+
def initialize data
|
|
10
|
+
@data = data
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.load path
|
|
14
|
+
new(JSON.parse(File.read(path)))
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def get pointer
|
|
18
|
+
SchemaGraphy::DataQuery::JSONPointer.resolve(@data, pointer)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
Reference = PathReference
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{%- assign pname = include.property[0] %}
|
|
2
|
+
{%- assign pbody = include.property[1] %}
|
|
3
|
+
{%- assign pid = include.ppty_path | replace: "__", "_" | replace: "<", "_" | replace: ">", "_" | replace: "$", "DOLLARSIGN_" | prepend: "conf_ppty_" %}
|
|
4
|
+
{%- assign ppath = include.ppty_path | replace: "__", "." | replace: "DOLLARSIGN_", "$" %}
|
|
5
|
+
{%- case include.tier %}
|
|
6
|
+
{%- when 1 %}
|
|
7
|
+
{%- assign dlchars = "::" %}
|
|
8
|
+
{%- when 2 %}
|
|
9
|
+
{%- assign dlchars = ":::" %}
|
|
10
|
+
{%- when 3 %}
|
|
11
|
+
{%- assign dlchars = "::::" %}
|
|
12
|
+
{%- else %}
|
|
13
|
+
{%- assign dlchars = ";;" %}
|
|
14
|
+
{%- endcase %}
|
|
15
|
+
|
|
16
|
+
[[{{ pid }},config.{{ ppath }}]]
|
|
17
|
+
{{ ppath }}{{ dlchars }}
|
|
18
|
+
+
|
|
19
|
+
--
|
|
20
|
+
{{ pbody.desc }}
|
|
21
|
+
{%- if pbody.docs %}
|
|
22
|
+
{{ pbody.docs }}
|
|
23
|
+
{%- endif %}
|
|
24
|
+
|
|
25
|
+
[horizontal]
|
|
26
|
+
{%- if pbody.type %}
|
|
27
|
+
type;; {{ pbody.type }}
|
|
28
|
+
{%- endif %}
|
|
29
|
+
{%- if pbody.templating %}
|
|
30
|
+
templating;; {% if pbody.templating.default %}{{ pbody.templating.default }}{% if pbody.templating.delay %}, {% endif %}{% endif %}{% if pbody.templating.delay %} delayed{% else %} immediate{% endif %} rendering
|
|
31
|
+
{%- endif %}
|
|
32
|
+
{%- if pbody.dflt != nil %}
|
|
33
|
+
default;;
|
|
34
|
+
+
|
|
35
|
+
{%- if pbody.dflt.first %}
|
|
36
|
+
{%- if pbody.dflt.size > 0 %}
|
|
37
|
+
....
|
|
38
|
+
{%- for item in pbody.dflt %}
|
|
39
|
+
- {{ item }}
|
|
40
|
+
{%- endfor %}
|
|
41
|
+
....
|
|
42
|
+
{%- else %}
|
|
43
|
+
[_empty array_]
|
|
44
|
+
{%- endif %}
|
|
45
|
+
{%- else %}
|
|
46
|
+
{%- if pbody.dflt contains '
|
|
47
|
+
' or pbody.dflt.size > 20 %}
|
|
48
|
+
....
|
|
49
|
+
{{ pbody.dflt | trim }}
|
|
50
|
+
....
|
|
51
|
+
{%- else %}
|
|
52
|
+
`+++{{ pbody.dflt | trim }}+++`
|
|
53
|
+
{%- endif %}
|
|
54
|
+
{%- endif %}
|
|
55
|
+
{%- endif %}
|
|
56
|
+
path;; `xref:{{ pid }}[{{ ppath | prepend: "config." }}]`
|
|
57
|
+
--
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{%- for property in config_def.properties -%}
|
|
2
|
+
{%- assign ppty_key1 = property[0] -%}
|
|
3
|
+
{%- assign ppty_path_t1 = ppty_key1 -%}
|
|
4
|
+
{%- include config-property.adoc.liquid
|
|
5
|
+
test_string="test string"
|
|
6
|
+
property=property
|
|
7
|
+
tier=1
|
|
8
|
+
ppty_path=ppty_path_t1 -%}
|
|
9
|
+
|
|
10
|
+
{%- assign props_t2 = property[1].properties -%}
|
|
11
|
+
{%- if props_t2 -%}
|
|
12
|
+
{%- for propty in props_t2 -%}
|
|
13
|
+
{%- assign ppty_key2 = propty[0] -%}
|
|
14
|
+
{%- assign ppty_path_t2 = ppty_path_t1 | append: '__' | append: ppty_key2 -%}
|
|
15
|
+
{%- include config-property.adoc.liquid
|
|
16
|
+
property=propty
|
|
17
|
+
tier=2
|
|
18
|
+
ppty_path=ppty_path_t2 -%}
|
|
19
|
+
|
|
20
|
+
{%- assign props_t3 = propty[1].properties -%}
|
|
21
|
+
{%- if props_t2 -%}
|
|
22
|
+
{%- for ppty in props_t3 -%}
|
|
23
|
+
{%- assign ppty_key3 = ppty[0] -%}
|
|
24
|
+
{%- assign ppty_path_t3 = ppty_path_t2 | append: '__' | append: ppty_key3 -%}
|
|
25
|
+
{%- include config-property.adoc.liquid
|
|
26
|
+
property=ppty
|
|
27
|
+
tier=3
|
|
28
|
+
ppty_path=ppty_path_t3 -%}
|
|
29
|
+
{%- endfor -%}
|
|
30
|
+
{%- endif -%}
|
|
31
|
+
{%- endfor -%}
|
|
32
|
+
{%- endif -%}
|
|
33
|
+
{%- endfor %}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Sample configuration file generated from the configuration definition.
|
|
2
|
+
# All values are upstream (gem) defaults.
|
|
3
|
+
# Properties without defaults are commented out.
|
|
4
|
+
{%- for property_t1 in config_def.properties %}
|
|
5
|
+
{%- assign key1 = property_t1[0] %}
|
|
6
|
+
{%- assign val1 = property_t1[1] %}
|
|
7
|
+
{%- assign dflt1 = val1.dflt %}
|
|
8
|
+
{%- assign key1_first_char = key1 | slice: 0, 1 %}
|
|
9
|
+
{%- if key1_first_char == '<' or dflt1 == nil and val1.properties == nil %}
|
|
10
|
+
{%- assign nulled1 = true %}
|
|
11
|
+
{%- else %}
|
|
12
|
+
{%- assign nulled1 = false %}
|
|
13
|
+
{%- endif %}
|
|
14
|
+
{%- include sample-property.yaml.liquid property=property_t1 tier=1 nulled=nulled1 %}
|
|
15
|
+
{%- assign sub_properties_t2 = val1.properties %}
|
|
16
|
+
{%- if sub_properties_t2 %}
|
|
17
|
+
{%- for property_t2 in sub_properties_t2 %}
|
|
18
|
+
{%- assign key2 = property_t2[0] %}
|
|
19
|
+
{%- assign val2 = property_t2[1] %}
|
|
20
|
+
{%- assign dflt2 = val2.dflt %}
|
|
21
|
+
{%- assign key2_first_char = key2 | slice: 0, 1 %}
|
|
22
|
+
{%- if nulled1 or (key2_first_char == '<' or (dflt2 == nil and val2.properties == nil)) %}
|
|
23
|
+
{%- assign nulled2 = true %}
|
|
24
|
+
{%- else %}
|
|
25
|
+
{%- assign nulled2 = false %}
|
|
26
|
+
{%- endif %}
|
|
27
|
+
{%- include sample-property.yaml.liquid property=property_t2 tier=2 nulled=nulled2 %}
|
|
28
|
+
|
|
29
|
+
{%- assign sub_properties_t3 = val2.properties %}
|
|
30
|
+
{%- if sub_properties_t3 %}
|
|
31
|
+
{%- for property_t3 in sub_properties_t3 %}
|
|
32
|
+
{%- assign key3 = property_t3[0] %}
|
|
33
|
+
{%- assign val3 = property_t3[1] %}
|
|
34
|
+
{%- assign dflt3 = val3.dflt %}
|
|
35
|
+
{%- assign key3_first_char = key3 | slice: 0, 1 %}
|
|
36
|
+
{%- if nulled2 or (key3_first_char == '<' or (dflt3 == nil and val3.properties == nil)) %}
|
|
37
|
+
{%- assign nulled3 = true %}
|
|
38
|
+
{%- else %}
|
|
39
|
+
{%- assign nulled3 = false %}
|
|
40
|
+
{%- endif %}
|
|
41
|
+
{%- include sample-property.yaml.liquid property=property_t3 tier=3 nulled=nulled3 %}
|
|
42
|
+
{%- endfor %}
|
|
43
|
+
{%- endif %}
|
|
44
|
+
{%- endfor %}
|
|
45
|
+
{%- endif %}
|
|
46
|
+
{%- endfor %}
|