expressir 2.1.30 → 2.1.31
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/.github/workflows/docs.yml +98 -0
- data/.github/workflows/links.yml +100 -0
- data/.github/workflows/rake.yml +4 -0
- data/.github/workflows/release.yml +5 -0
- data/.github/workflows/validate_schemas.yml +1 -1
- data/.gitignore +3 -0
- data/.rubocop.yml +1 -1
- data/.rubocop_todo.yml +244 -39
- data/Gemfile +2 -1
- data/README.adoc +621 -54
- data/docs/Gemfile +12 -0
- data/docs/_config.yml +141 -0
- data/docs/_guides/changes/changes-format.adoc +778 -0
- data/docs/_guides/changes/importing-eengine.adoc +898 -0
- data/docs/_guides/changes/index.adoc +396 -0
- data/docs/_guides/changes/programmatic-usage.adoc +1038 -0
- data/docs/_guides/changes/validating-changes.adoc +681 -0
- data/docs/_guides/cli/benchmark-performance.adoc +834 -0
- data/docs/_guides/cli/coverage-analysis.adoc +921 -0
- data/docs/_guides/cli/format-schemas.adoc +547 -0
- data/docs/_guides/cli/index.adoc +8 -0
- data/docs/_guides/cli/managing-changes.adoc +927 -0
- data/docs/_guides/cli/validate-ascii.adoc +645 -0
- data/docs/_guides/cli/validate-schemas.adoc +534 -0
- data/docs/_guides/index.adoc +165 -0
- data/docs/_guides/ler/creating-packages.adoc +664 -0
- data/docs/_guides/ler/index.adoc +305 -0
- data/docs/_guides/ler/loading-packages.adoc +707 -0
- data/docs/_guides/ler/package-formats.adoc +748 -0
- data/docs/_guides/ler/querying-packages.adoc +826 -0
- data/docs/_guides/ler/validating-packages.adoc +750 -0
- data/docs/_guides/liquid/basic-templates.adoc +813 -0
- data/docs/_guides/liquid/documentation-generation.adoc +1042 -0
- data/docs/_guides/liquid/drops-reference.adoc +829 -0
- data/docs/_guides/liquid/filters-and-tags.adoc +912 -0
- data/docs/_guides/liquid/index.adoc +468 -0
- data/docs/_guides/manifests/creating-manifests.adoc +483 -0
- data/docs/_guides/manifests/index.adoc +307 -0
- data/docs/_guides/manifests/resolving-manifests.adoc +557 -0
- data/docs/_guides/manifests/validating-manifests.adoc +713 -0
- data/docs/_guides/ruby-api/formatting-schemas.adoc +605 -0
- data/docs/_guides/ruby-api/index.adoc +257 -0
- data/docs/_guides/ruby-api/parsing-files.adoc +421 -0
- data/docs/_guides/ruby-api/search-engine.adoc +609 -0
- data/docs/_guides/ruby-api/working-with-repository.adoc +577 -0
- data/docs/_pages/data-model.adoc +665 -0
- data/docs/_pages/express-language.adoc +506 -0
- data/docs/_pages/getting-started.adoc +414 -0
- data/docs/_pages/index.adoc +116 -0
- data/docs/_pages/introduction.adoc +256 -0
- data/docs/_pages/ler-packages.adoc +837 -0
- data/docs/_pages/parsers.adoc +683 -0
- data/docs/_pages/schema-manifests.adoc +431 -0
- data/docs/_references/index.adoc +228 -0
- data/docs/_tutorials/creating-ler-package.adoc +735 -0
- data/docs/_tutorials/documentation-coverage.adoc +795 -0
- data/docs/_tutorials/index.adoc +221 -0
- data/docs/_tutorials/liquid-templates.adoc +806 -0
- data/docs/_tutorials/parsing-your-first-schema.adoc +522 -0
- data/docs/_tutorials/querying-schemas.adoc +751 -0
- data/docs/_tutorials/working-with-multiple-schemas.adoc +676 -0
- data/docs/index.adoc +242 -0
- data/docs/lychee.toml +84 -0
- data/examples/demo_ler_usage.sh +86 -0
- data/examples/ler/README.md +111 -0
- data/examples/ler/simple_example.ler +0 -0
- data/examples/ler/simple_schema.exp +33 -0
- data/examples/ler_build.rb +75 -0
- data/examples/ler_cli.rb +79 -0
- data/examples/ler_demo_complete.rb +276 -0
- data/examples/ler_query.rb +91 -0
- data/examples/ler_query_examples.rb +305 -0
- data/examples/ler_stats.rb +81 -0
- data/examples/phase3_demo.rb +159 -0
- data/examples/query_demo_simple.rb +131 -0
- data/expressir.gemspec +2 -0
- data/lib/expressir/cli.rb +12 -4
- data/lib/expressir/commands/manifest.rb +427 -0
- data/lib/expressir/commands/package.rb +1274 -0
- data/lib/expressir/commands/validate.rb +70 -37
- data/lib/expressir/commands/validate_ascii.rb +607 -0
- data/lib/expressir/commands/validate_load.rb +88 -0
- data/lib/expressir/express/formatter.rb +5 -1
- data/lib/expressir/express/formatters/remark_item_formatter.rb +25 -0
- data/lib/expressir/express/parser.rb +33 -0
- data/lib/expressir/manifest/resolver.rb +213 -0
- data/lib/expressir/manifest/validator.rb +195 -0
- data/lib/expressir/model/declarations/entity.rb +6 -0
- data/lib/expressir/model/dependency_resolver.rb +270 -0
- data/lib/expressir/model/indexes/entity_index.rb +103 -0
- data/lib/expressir/model/indexes/reference_index.rb +148 -0
- data/lib/expressir/model/indexes/type_index.rb +149 -0
- data/lib/expressir/model/interface_validator.rb +384 -0
- data/lib/expressir/model/repository.rb +400 -5
- data/lib/expressir/model/repository_validator.rb +295 -0
- data/lib/expressir/model/search_engine.rb +525 -0
- data/lib/expressir/model.rb +4 -94
- data/lib/expressir/package/builder.rb +200 -0
- data/lib/expressir/package/metadata.rb +81 -0
- data/lib/expressir/package/reader.rb +165 -0
- data/lib/expressir/schema_manifest.rb +11 -1
- data/lib/expressir/version.rb +1 -1
- data/lib/expressir.rb +15 -2
- metadata +114 -4
- data/docs/benchmarking.adoc +0 -107
- data/docs/liquid_drops.adoc +0 -1547
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
require_relative "formatters/remark_item_formatter"
|
|
2
|
+
|
|
1
3
|
module Expressir
|
|
2
4
|
module Express
|
|
3
5
|
class Formatter
|
|
6
|
+
include RemarkItemFormatter
|
|
7
|
+
|
|
4
8
|
INDENT_CHAR = " ".freeze
|
|
5
9
|
INDENT_WIDTH = 2
|
|
6
10
|
INDENT = INDENT_CHAR * INDENT_WIDTH
|
|
@@ -194,7 +198,7 @@ module Expressir
|
|
|
194
198
|
when Model::Declarations::InterfacedItem
|
|
195
199
|
# not implemented yet
|
|
196
200
|
when Model::Declarations::RemarkItem
|
|
197
|
-
|
|
201
|
+
format_remark_item(node)
|
|
198
202
|
when Model::Cache
|
|
199
203
|
# not implemented yet
|
|
200
204
|
when Model::ModelElement
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Expressir
|
|
2
|
+
module Express
|
|
3
|
+
# Formatter for RemarkItem declarations
|
|
4
|
+
module RemarkItemFormatter
|
|
5
|
+
# Format a RemarkItem as an EXPRESS remark
|
|
6
|
+
# @param node [Model::Declarations::RemarkItem] The remark item to format
|
|
7
|
+
# @return [String] Formatted remark
|
|
8
|
+
def format_remark_item(node)
|
|
9
|
+
return "" unless node.remarks&.any?
|
|
10
|
+
|
|
11
|
+
# Check if any remark contains newlines
|
|
12
|
+
has_newlines = node.remarks.any? { |r| r.to_s.include?("\n") }
|
|
13
|
+
|
|
14
|
+
if has_newlines
|
|
15
|
+
# Multi-line format: (*"path" remarks *)
|
|
16
|
+
remarks_text = node.remarks.join("\n")
|
|
17
|
+
"(*\"#{node.path}\"\n#{remarks_text}\n*)"
|
|
18
|
+
else
|
|
19
|
+
# Single-line format: --"path"
|
|
20
|
+
"--\"#{node.path}\""
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -680,6 +680,39 @@ root_path: nil)
|
|
|
680
680
|
|
|
681
681
|
@repository
|
|
682
682
|
end
|
|
683
|
+
|
|
684
|
+
# Parses Express content string into an Express model
|
|
685
|
+
# @param [String] content Express content as string
|
|
686
|
+
# @param [Boolean] skip_references skip resolving references
|
|
687
|
+
# @param [Boolean] include_source attach original source code to model elements
|
|
688
|
+
# @return [Model::Repository]
|
|
689
|
+
# @raise [SchemaParseFailure] if the content fails to parse
|
|
690
|
+
def self.from_exp(content, skip_references: nil, include_source: nil)
|
|
691
|
+
begin
|
|
692
|
+
ast = Parser.new.parse content
|
|
693
|
+
rescue Parslet::ParseFailed => e
|
|
694
|
+
raise Error::SchemaParseFailure.new("(from string)", e)
|
|
695
|
+
end
|
|
696
|
+
|
|
697
|
+
visitor = Expressir::Express::Visitor.new(content,
|
|
698
|
+
include_source: include_source)
|
|
699
|
+
repository = visitor.visit_ast ast, :top
|
|
700
|
+
|
|
701
|
+
repository.schemas.each do |schema|
|
|
702
|
+
schema.file = nil
|
|
703
|
+
schema.file_basename = nil
|
|
704
|
+
schema.formatted = schema.to_s(no_remarks: true)
|
|
705
|
+
end
|
|
706
|
+
|
|
707
|
+
unless skip_references
|
|
708
|
+
Expressir::Benchmark.measure_references do
|
|
709
|
+
resolve_references_model_visitor = ResolveReferencesModelVisitor.new
|
|
710
|
+
resolve_references_model_visitor.visit(repository)
|
|
711
|
+
end
|
|
712
|
+
end
|
|
713
|
+
|
|
714
|
+
repository
|
|
715
|
+
end
|
|
683
716
|
end
|
|
684
717
|
end
|
|
685
718
|
end
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../model/dependency_resolver"
|
|
4
|
+
|
|
5
|
+
module Expressir
|
|
6
|
+
module Manifest
|
|
7
|
+
# Resolves schema paths in manifests using pattern-based path discovery
|
|
8
|
+
# Attempts to find file paths for schemas with missing or empty paths
|
|
9
|
+
class Resolver
|
|
10
|
+
attr_reader :manifest, :options, :base_dirs
|
|
11
|
+
|
|
12
|
+
# Initialize resolver
|
|
13
|
+
# @param manifest [SchemaManifest] The manifest to resolve
|
|
14
|
+
# @param options [Hash] Resolution options
|
|
15
|
+
# @option options [Array<String>] :base_dirs Base directories to search
|
|
16
|
+
# @option options [Boolean] :verbose Enable verbose output
|
|
17
|
+
def initialize(manifest, options = {})
|
|
18
|
+
@manifest = manifest
|
|
19
|
+
@options = options
|
|
20
|
+
@base_dirs = extract_base_dirs_from_manifest
|
|
21
|
+
|
|
22
|
+
# Override with explicit base_dirs if provided
|
|
23
|
+
if options[:base_dirs]
|
|
24
|
+
@base_dirs = Array(options[:base_dirs]).map do |d|
|
|
25
|
+
File.expand_path(d)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Resolve paths for all schemas in manifest
|
|
31
|
+
# Works like manifest create - resolves all dependencies from schemas with paths
|
|
32
|
+
# @return [SchemaManifest] New manifest with resolved paths and dependencies
|
|
33
|
+
def resolve_paths
|
|
34
|
+
# Create new manifest to avoid modifying original
|
|
35
|
+
resolved_manifest = SchemaManifest.new
|
|
36
|
+
|
|
37
|
+
# Track all resolved schema paths
|
|
38
|
+
all_resolved_paths = Set.new
|
|
39
|
+
all_unresolved = []
|
|
40
|
+
|
|
41
|
+
# Find schemas with valid paths to use as roots
|
|
42
|
+
root_schemas = manifest.schemas.select do |s|
|
|
43
|
+
!s.path.nil? && !s.path.empty? && File.exist?(s.path)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
if root_schemas.empty?
|
|
47
|
+
# No roots, just try to resolve individual schemas
|
|
48
|
+
say "No valid schema paths found, attempting simple path resolution..." if options[:verbose]
|
|
49
|
+
return resolve_paths_simple
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
say "Resolving dependencies from #{root_schemas.size} root schema(s)..." if options[:verbose]
|
|
53
|
+
say_base_dirs
|
|
54
|
+
|
|
55
|
+
# For each root schema, resolve all its dependencies
|
|
56
|
+
root_schemas.each do |root_entry|
|
|
57
|
+
resolver = Model::DependencyResolver.new(
|
|
58
|
+
base_dirs: @base_dirs,
|
|
59
|
+
schema_registry: build_schema_registry,
|
|
60
|
+
verbose: options[:verbose],
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# Resolve all dependencies for this root
|
|
64
|
+
resolved_paths = resolver.resolve_dependencies(root_entry.path)
|
|
65
|
+
all_resolved_paths.merge(resolved_paths)
|
|
66
|
+
all_unresolved.concat(resolver.unresolved)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Add all resolved schemas to manifest
|
|
70
|
+
all_resolved_paths.each do |path|
|
|
71
|
+
schema_id = extract_schema_name(path)
|
|
72
|
+
resolved_manifest.schemas << SchemaManifestEntry.new(
|
|
73
|
+
id: schema_id,
|
|
74
|
+
path: path,
|
|
75
|
+
)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Add unresolved schemas with empty paths
|
|
79
|
+
all_unresolved.uniq { |e| e[:schema_name] }.each do |entry|
|
|
80
|
+
# Only add if not already in manifest
|
|
81
|
+
unless resolved_manifest.schemas.any? do |s|
|
|
82
|
+
s.id == entry[:schema_name]
|
|
83
|
+
end
|
|
84
|
+
resolved_manifest.schemas << SchemaManifestEntry.new(
|
|
85
|
+
id: entry[:schema_name],
|
|
86
|
+
path: "",
|
|
87
|
+
)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
resolved_manifest
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Simple path resolution - just try to find missing paths
|
|
95
|
+
# Used when no valid root schemas are available
|
|
96
|
+
# @return [SchemaManifest] New manifest with resolved paths
|
|
97
|
+
def resolve_paths_simple
|
|
98
|
+
resolved_manifest = SchemaManifest.new
|
|
99
|
+
|
|
100
|
+
resolver = Model::DependencyResolver.new(
|
|
101
|
+
base_dirs: @base_dirs,
|
|
102
|
+
schema_registry: build_schema_registry,
|
|
103
|
+
verbose: options[:verbose],
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
manifest.schemas.each do |schema_entry|
|
|
107
|
+
resolved_entry = resolve_schema_entry(schema_entry, resolver)
|
|
108
|
+
resolved_manifest.schemas << resolved_entry
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
resolved_manifest
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Get resolution statistics
|
|
115
|
+
# @return [Hash] Resolution statistics
|
|
116
|
+
def statistics
|
|
117
|
+
unresolved_count = manifest.schemas.count do |s|
|
|
118
|
+
s.path.nil? || s.path.empty?
|
|
119
|
+
end
|
|
120
|
+
{
|
|
121
|
+
total_schemas: manifest.schemas.size,
|
|
122
|
+
resolved: manifest.schemas.size - unresolved_count,
|
|
123
|
+
unresolved: unresolved_count,
|
|
124
|
+
base_dirs: @base_dirs,
|
|
125
|
+
}
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
private
|
|
129
|
+
|
|
130
|
+
# Extract the actual schema name from a schema file by parsing it
|
|
131
|
+
# @param schema_path [String] Path to the schema file
|
|
132
|
+
# @return [String] The schema name declared in the file
|
|
133
|
+
def extract_schema_name(schema_path)
|
|
134
|
+
require_relative "../express/parser"
|
|
135
|
+
repo = Expressir::Express::Parser.from_file(schema_path)
|
|
136
|
+
schema = repo.schemas.first
|
|
137
|
+
schema&.id || File.basename(schema_path, ".*")
|
|
138
|
+
rescue StandardError
|
|
139
|
+
# Fallback to filename if parsing fails
|
|
140
|
+
File.basename(schema_path, ".*")
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Say message (only used when verbose is enabled)
|
|
144
|
+
def say(message)
|
|
145
|
+
puts message if options[:verbose]
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Say base directories with numbered labels
|
|
149
|
+
def say_base_dirs
|
|
150
|
+
if options[:verbose] && @base_dirs
|
|
151
|
+
say " Using base directories:"
|
|
152
|
+
@base_dirs.each_with_index do |dir, index|
|
|
153
|
+
say " - [source #{index + 1}]: #{dir}"
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Resolve a single schema entry
|
|
159
|
+
# @param schema_entry [SchemaManifestEntry] The entry to resolve
|
|
160
|
+
# @param resolver [Model::DependencyResolver] The resolver to use
|
|
161
|
+
# @return [SchemaManifestEntry] New entry with resolved path
|
|
162
|
+
def resolve_schema_entry(schema_entry, resolver)
|
|
163
|
+
# If path already exists and is valid, keep it
|
|
164
|
+
if schema_entry.path && !schema_entry.path.empty? && File.exist?(schema_entry.path)
|
|
165
|
+
return SchemaManifestEntry.new(
|
|
166
|
+
id: schema_entry.id,
|
|
167
|
+
path: schema_entry.path,
|
|
168
|
+
)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Try to resolve path using DependencyResolver
|
|
172
|
+
resolved_path = resolver.resolve_schema_location(
|
|
173
|
+
schema_entry.id,
|
|
174
|
+
"USE", # Kind doesn't matter for location resolution
|
|
175
|
+
nil, # No current path context
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
SchemaManifestEntry.new(
|
|
179
|
+
id: schema_entry.id,
|
|
180
|
+
path: resolved_path || schema_entry.path || "",
|
|
181
|
+
)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Build schema registry from manifest entries with valid paths
|
|
185
|
+
# Maps schema IDs to their file paths
|
|
186
|
+
# @return [Hash<String, String>] Schema name => path mapping
|
|
187
|
+
def build_schema_registry
|
|
188
|
+
registry = {}
|
|
189
|
+
manifest.schemas.each do |schema_entry|
|
|
190
|
+
next if schema_entry.id.nil?
|
|
191
|
+
next if schema_entry.path.nil? || schema_entry.path.empty?
|
|
192
|
+
next unless File.exist?(schema_entry.path)
|
|
193
|
+
|
|
194
|
+
registry[schema_entry.id] = schema_entry.path
|
|
195
|
+
end
|
|
196
|
+
registry
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Extract base directories from manifest schema paths
|
|
200
|
+
# @return [Array<String>] List of unique base directories
|
|
201
|
+
def extract_base_dirs_from_manifest
|
|
202
|
+
dirs = manifest.schemas
|
|
203
|
+
.reject { |s| s.path.nil? || s.path.empty? }
|
|
204
|
+
.select { |s| File.exist?(s.path) }
|
|
205
|
+
.map { |s| File.dirname(File.expand_path(s.path)) }
|
|
206
|
+
.uniq
|
|
207
|
+
|
|
208
|
+
# If no directories found, use current directory
|
|
209
|
+
dirs.empty? ? [Dir.pwd] : dirs
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../model/dependency_resolver"
|
|
4
|
+
|
|
5
|
+
module Expressir
|
|
6
|
+
module Manifest
|
|
7
|
+
# Validates schema manifests for completeness and integrity
|
|
8
|
+
# Performs three types of validation:
|
|
9
|
+
# 1. File existence - checks if schema files exist
|
|
10
|
+
# 2. Path completeness - warns about missing paths
|
|
11
|
+
# 3. Referential integrity - validates USE/REFERENCE FROM resolution
|
|
12
|
+
class Validator
|
|
13
|
+
attr_reader :manifest, :options
|
|
14
|
+
|
|
15
|
+
# Initialize validator
|
|
16
|
+
# @param manifest [SchemaManifest] The manifest to validate
|
|
17
|
+
# @param options [Hash] Validation options
|
|
18
|
+
# @option options [Boolean] :verbose Enable verbose output
|
|
19
|
+
def initialize(manifest, options = {})
|
|
20
|
+
@manifest = manifest
|
|
21
|
+
@options = options
|
|
22
|
+
@base_dirs = extract_base_dirs_from_manifest
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Validate that all schema files exist on disk
|
|
26
|
+
# @return [Array<Hash>] Array of error hashes with :schema, :path, :message
|
|
27
|
+
def validate_file_existence
|
|
28
|
+
errors = []
|
|
29
|
+
manifest.schemas.each do |schema_entry|
|
|
30
|
+
next if schema_entry.path.nil? || schema_entry.path.empty?
|
|
31
|
+
|
|
32
|
+
unless File.exist?(schema_entry.path)
|
|
33
|
+
errors << {
|
|
34
|
+
schema: schema_entry.id,
|
|
35
|
+
path: schema_entry.path,
|
|
36
|
+
message: "Schema file not found: #{schema_entry.path} (#{schema_entry.id})",
|
|
37
|
+
}
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
errors
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Validate that all schemas have paths specified
|
|
44
|
+
# @return [Array<Hash>] Array of warning hashes with :schema, :message
|
|
45
|
+
def validate_path_completeness
|
|
46
|
+
warnings = []
|
|
47
|
+
manifest.schemas.each do |schema_entry|
|
|
48
|
+
if schema_entry.path.nil? || schema_entry.path.empty?
|
|
49
|
+
warnings << {
|
|
50
|
+
schema: schema_entry.id,
|
|
51
|
+
message: "Schema '#{schema_entry.id}' has no path specified - please provide path",
|
|
52
|
+
}
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
warnings
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Validate that manifest IDs match actual schema names in files
|
|
59
|
+
# @return [Array<Hash>] Array of error hashes with :schema, :path, :actual_name, :message
|
|
60
|
+
def validate_schema_names
|
|
61
|
+
errors = []
|
|
62
|
+
manifest.schemas.each do |schema_entry|
|
|
63
|
+
next if schema_entry.path.nil? || schema_entry.path.empty?
|
|
64
|
+
next unless File.exist?(schema_entry.path)
|
|
65
|
+
|
|
66
|
+
begin
|
|
67
|
+
actual_name = extract_schema_name(schema_entry.path)
|
|
68
|
+
if actual_name != schema_entry.id
|
|
69
|
+
errors << {
|
|
70
|
+
schema: schema_entry.id,
|
|
71
|
+
path: schema_entry.path,
|
|
72
|
+
actual_name: actual_name,
|
|
73
|
+
message: "Schema ID mismatch: manifest has '#{schema_entry.id}' but file declares '#{actual_name}'",
|
|
74
|
+
}
|
|
75
|
+
end
|
|
76
|
+
rescue StandardError
|
|
77
|
+
# Skip validation if we can't parse the file
|
|
78
|
+
next
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
errors
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Validate referential integrity using DependencyResolver
|
|
85
|
+
# Checks that all USE FROM and REFERENCE FROM interfaces can be resolved
|
|
86
|
+
# @return [Array<Hash>] Array of error hashes with interface details
|
|
87
|
+
def validate_referential_integrity
|
|
88
|
+
errors = []
|
|
89
|
+
|
|
90
|
+
# Filter schemas to validate
|
|
91
|
+
schemas_to_validate = manifest.schemas.select do |s|
|
|
92
|
+
!s.path.nil? && !s.path.empty? && File.exist?(s.path)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Show header if verbose
|
|
96
|
+
if @options[:verbose]
|
|
97
|
+
puts "Validating referential integrity for #{schemas_to_validate.size} schemas..."
|
|
98
|
+
puts ""
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Create resolver ONCE for all schemas (efficiency)
|
|
102
|
+
# Share verbose output behavior with manifest create command (DRY)
|
|
103
|
+
resolver = Model::DependencyResolver.new(
|
|
104
|
+
base_dirs: @base_dirs,
|
|
105
|
+
schema_registry: build_schema_registry,
|
|
106
|
+
verbose: @options[:verbose], # Let resolver show detailed interface resolution
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
schemas_to_validate.each do |schema_entry|
|
|
110
|
+
# Extract interfaces from this schema
|
|
111
|
+
begin
|
|
112
|
+
interfaces = resolver.extract_interfaces(schema_entry.path)
|
|
113
|
+
rescue StandardError => e
|
|
114
|
+
errors << {
|
|
115
|
+
schema: schema_entry.id,
|
|
116
|
+
path: schema_entry.path,
|
|
117
|
+
message: "Failed to parse schema: #{e.message}",
|
|
118
|
+
}
|
|
119
|
+
next
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Check each interface can be resolved
|
|
123
|
+
# The resolver will print verbose output for each resolution when verbose=true
|
|
124
|
+
interfaces.each do |interface|
|
|
125
|
+
# Print schema name context if verbose (matches create command style)
|
|
126
|
+
if @options[:verbose]
|
|
127
|
+
print " #{interface[:kind]} FROM #{interface[:schema_name]} (in #{schema_entry.id}): "
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
resolved_path = resolver.resolve_schema_location(
|
|
131
|
+
interface[:schema_name],
|
|
132
|
+
interface[:kind],
|
|
133
|
+
schema_entry.path,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
if resolved_path.nil?
|
|
137
|
+
puts "\e[31m✗ not found\e[0m" if @options[:verbose]
|
|
138
|
+
|
|
139
|
+
errors << {
|
|
140
|
+
schema: schema_entry.id,
|
|
141
|
+
path: schema_entry.path,
|
|
142
|
+
interface_kind: interface[:kind],
|
|
143
|
+
referenced_schema: interface[:schema_name],
|
|
144
|
+
message: "Cannot resolve #{interface[:kind]} FROM #{interface[:schema_name]} in #{schema_entry.id}",
|
|
145
|
+
}
|
|
146
|
+
elsif @options[:verbose]
|
|
147
|
+
relative_path = File.basename(resolved_path)
|
|
148
|
+
puts "\e[32m✓\e[0m #{relative_path}"
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
errors
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
private
|
|
157
|
+
|
|
158
|
+
# Extract the actual schema name from a schema file by parsing it
|
|
159
|
+
# @param schema_path [String] Path to the schema file
|
|
160
|
+
# @return [String] The schema name declared in the file
|
|
161
|
+
def extract_schema_name(schema_path)
|
|
162
|
+
require_relative "../express/parser"
|
|
163
|
+
repo = Expressir::Express::Parser.from_file(schema_path)
|
|
164
|
+
schema = repo.schemas.first
|
|
165
|
+
schema&.id || File.basename(schema_path, ".*")
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Build schema registry from manifest entries
|
|
169
|
+
# Maps schema IDs to their file paths
|
|
170
|
+
# @return [Hash<String, String>] Schema name => path mapping
|
|
171
|
+
def build_schema_registry
|
|
172
|
+
registry = {}
|
|
173
|
+
manifest.schemas.each do |schema_entry|
|
|
174
|
+
next if schema_entry.id.nil?
|
|
175
|
+
next if schema_entry.path.nil? || schema_entry.path.empty?
|
|
176
|
+
|
|
177
|
+
registry[schema_entry.id] = schema_entry.path
|
|
178
|
+
end
|
|
179
|
+
registry
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Extract base directories from manifest schema paths
|
|
183
|
+
# @return [Array<String>] List of unique base directories
|
|
184
|
+
def extract_base_dirs_from_manifest
|
|
185
|
+
dirs = manifest.schemas
|
|
186
|
+
.reject { |s| s.path.nil? || s.path.empty? }
|
|
187
|
+
.map { |s| File.dirname(File.expand_path(s.path)) }
|
|
188
|
+
.uniq
|
|
189
|
+
|
|
190
|
+
# If no directories found, use current directory
|
|
191
|
+
dirs.empty? ? [Dir.pwd] : dirs
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
@@ -10,6 +10,8 @@ module Expressir
|
|
|
10
10
|
attribute :supertype_expression, ModelElement
|
|
11
11
|
attribute :subtype_of, ModelElement, collection: true
|
|
12
12
|
attribute :attributes, Attribute, collection: true
|
|
13
|
+
attribute :derived_attributes, DerivedAttribute, collection: true
|
|
14
|
+
attribute :inverse_attributes, InverseAttribute, collection: true
|
|
13
15
|
attribute :unique_rules, UniqueRule, collection: true
|
|
14
16
|
attribute :where_rules, WhereRule, collection: true
|
|
15
17
|
attribute :informal_propositions, InformalPropositionRule,
|
|
@@ -22,6 +24,8 @@ module Expressir
|
|
|
22
24
|
map "supertype_expression", to: :supertype_expression
|
|
23
25
|
map "subtype_of", to: :subtype_of
|
|
24
26
|
map "attributes", to: :attributes
|
|
27
|
+
map "derived_attributes", to: :derived_attributes
|
|
28
|
+
map "inverse_attributes", to: :inverse_attributes
|
|
25
29
|
map "unique_rules", to: :unique_rules
|
|
26
30
|
map "where_rules", to: :where_rules
|
|
27
31
|
map "informal_propositions", to: :informal_propositions
|
|
@@ -31,6 +35,8 @@ module Expressir
|
|
|
31
35
|
def children
|
|
32
36
|
[
|
|
33
37
|
*attributes,
|
|
38
|
+
*derived_attributes,
|
|
39
|
+
*inverse_attributes,
|
|
34
40
|
*unique_rules,
|
|
35
41
|
*where_rules,
|
|
36
42
|
*informal_propositions,
|