suma 0.2.5 → 0.3.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/.github/workflows/rake.yml +3 -0
- data/.github/workflows/release.yml +5 -1
- data/.gitignore +10 -1
- data/.rubocop_todo.yml +237 -28
- data/CLAUDE.md +102 -0
- data/Gemfile +3 -1
- data/README.adoc +188 -1
- data/exe/suma +1 -1
- data/lib/suma/cli/build.rb +2 -8
- data/lib/suma/cli/check_svg_quality.rb +172 -0
- data/lib/suma/cli/compare.rb +6 -158
- data/lib/suma/cli/convert_jsdai.rb +0 -2
- data/lib/suma/cli/core.rb +119 -0
- data/lib/suma/cli/export.rb +1 -10
- data/lib/suma/cli/extract_terms.rb +10 -654
- data/lib/suma/cli/generate_register.rb +34 -0
- data/lib/suma/cli/generate_schemas.rb +8 -124
- data/lib/suma/cli/reformat.rb +0 -1
- data/lib/suma/cli/validate.rb +0 -2
- data/lib/suma/cli/validate_links.rb +14 -291
- data/lib/suma/cli.rb +12 -102
- data/lib/suma/collection_config.rb +0 -2
- data/lib/suma/collection_manifest.rb +7 -111
- data/lib/suma/eengine/wrapper.rb +0 -1
- data/lib/suma/eengine.rb +8 -0
- data/lib/suma/express_schema.rb +43 -31
- data/lib/suma/jsdai/figure.rb +0 -3
- data/lib/suma/jsdai/figure_xml.rb +12 -9
- data/lib/suma/jsdai.rb +5 -8
- data/lib/suma/link_validator.rb +211 -0
- data/lib/suma/manifest_traverser.rb +92 -0
- data/lib/suma/processor.rb +76 -105
- data/lib/suma/register_manifest_generator.rb +163 -0
- data/lib/suma/schema_category.rb +83 -0
- data/lib/suma/schema_collection.rb +28 -63
- data/lib/suma/schema_comparer.rb +117 -0
- data/lib/suma/schema_compiler.rb +86 -0
- data/lib/suma/schema_discovery.rb +75 -0
- data/lib/suma/schema_exporter.rb +7 -35
- data/lib/suma/schema_index.rb +53 -0
- data/lib/suma/schema_manifest_generator.rb +113 -0
- data/lib/suma/schema_naming.rb +111 -0
- data/lib/suma/schema_template/document.rb +141 -0
- data/lib/suma/schema_template/plain.rb +46 -0
- data/lib/suma/schema_template.rb +19 -0
- data/lib/suma/svg_quality/batch_report.rb +78 -0
- data/lib/suma/svg_quality/formatters/json_formatter.rb +30 -0
- data/lib/suma/svg_quality/formatters/terminal_formatter.rb +168 -0
- data/lib/suma/svg_quality/formatters/yaml_formatter.rb +32 -0
- data/lib/suma/svg_quality/formatters.rb +12 -0
- data/lib/suma/svg_quality/report.rb +52 -0
- data/lib/suma/svg_quality.rb +30 -0
- data/lib/suma/term_extractor.rb +466 -0
- data/lib/suma/urn.rb +61 -0
- data/lib/suma/utils.rb +10 -2
- data/lib/suma/version.rb +1 -1
- data/lib/suma.rb +34 -5
- data/suma.gemspec +3 -2
- metadata +53 -9
- data/lib/suma/export_standalone_schema.rb +0 -14
- data/lib/suma/schema_attachment.rb +0 -130
- data/lib/suma/schema_document.rb +0 -132
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "thor"
|
|
4
|
+
|
|
5
|
+
module Suma
|
|
6
|
+
module Cli
|
|
7
|
+
class GenerateRegister < Thor
|
|
8
|
+
desc "generate_register SCHEMA_MANIFEST_FILE OUTPUT_PATH",
|
|
9
|
+
"Generate a Glossarist register.yaml with hierarchical sections"
|
|
10
|
+
option :language_code, type: :string, default: "eng", aliases: "-l",
|
|
11
|
+
desc: "Language code for section names"
|
|
12
|
+
option :urn, type: :string, required: true, aliases: "-u",
|
|
13
|
+
desc: "URN prefix for the dataset"
|
|
14
|
+
option :id, type: :string, required: true,
|
|
15
|
+
desc: "Dataset identifier (e.g. iso10303-2-express)"
|
|
16
|
+
option :ref, type: :string, required: true,
|
|
17
|
+
desc: "Human-readable reference label"
|
|
18
|
+
option :owner, type: :string, default: Suma::RegisterManifestGenerator::DEFAULT_OWNER,
|
|
19
|
+
desc: "Owner of the dataset (e.g. 'ISO/TC 184/SC 4')"
|
|
20
|
+
|
|
21
|
+
def generate_register(schema_manifest_file, output_path)
|
|
22
|
+
RegisterManifestGenerator.new(
|
|
23
|
+
schema_manifest_file,
|
|
24
|
+
output_path,
|
|
25
|
+
urn: options[:urn],
|
|
26
|
+
id: options[:id],
|
|
27
|
+
ref: options[:ref],
|
|
28
|
+
language_code: options[:language_code],
|
|
29
|
+
owner: options[:owner],
|
|
30
|
+
).generate
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -1,12 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "thor"
|
|
2
|
-
require_relative "../thor_ext"
|
|
3
|
-
require "fileutils"
|
|
4
|
-
require "yaml"
|
|
5
|
-
require "pathname"
|
|
6
4
|
|
|
7
5
|
module Suma
|
|
8
6
|
module Cli
|
|
9
|
-
# GenerateSchemas command to generate Schemas YAML by Metanorma YAML
|
|
10
7
|
class GenerateSchemas < Thor
|
|
11
8
|
desc "generate_schemas METANORMA_MANIFEST_FILE SCHEMA_MANIFEST_FILE",
|
|
12
9
|
"Generate EXPRESS schema manifest file from Metanorma site manifest"
|
|
@@ -14,125 +11,12 @@ module Suma
|
|
|
14
11
|
desc: "Exclude schemas paths by pattern " \
|
|
15
12
|
"(e.g. `*_lf.exp`)"
|
|
16
13
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
raise Errno::ENOENT, "Specified file `#{metanorma_manifest_file}` " \
|
|
24
|
-
"not found."
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
unless File.file?(metanorma_manifest_file)
|
|
28
|
-
raise ArgumentError, "Specified path `#{metanorma_manifest_file}` " \
|
|
29
|
-
"is not a file."
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
[metanorma_manifest_file, schema_manifest_file].each do |file|
|
|
33
|
-
if !YAML_FILE_EXTENSIONS.include?(File.extname(file))
|
|
34
|
-
raise ArgumentError, "Specified file `#{file}` is not a YAML file."
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
run(
|
|
39
|
-
metanorma_manifest_file, schema_manifest_file,
|
|
40
|
-
exclude_paths: options[:exclude_paths]
|
|
41
|
-
)
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
private
|
|
45
|
-
|
|
46
|
-
def run(metanorma_manifest_file, schema_manifest_file, exclude_paths: nil)
|
|
47
|
-
metanorma_data = load_yaml(metanorma_manifest_file)
|
|
48
|
-
collection_files = metanorma_data["metanorma"]["source"]["files"]
|
|
49
|
-
manifest_files = load_manifest_files(collection_files)
|
|
50
|
-
all_schemas = load_project_schemas(manifest_files, exclude_paths,
|
|
51
|
-
schema_manifest_file)
|
|
52
|
-
all_schemas["schemas"] = all_schemas["schemas"].sort.to_h
|
|
53
|
-
output_data(all_schemas, schema_manifest_file)
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def output_data(all_schemas, path)
|
|
57
|
-
puts "Writing the Schemas YAML file to #{File.expand_path(path)}..."
|
|
58
|
-
# debug use only
|
|
59
|
-
# puts all_schemas.to_yaml
|
|
60
|
-
File.write(File.expand_path(path), all_schemas.to_yaml)
|
|
61
|
-
puts "Writing the Schemas YAML file to #{File.expand_path(path)}...Done"
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def load_yaml(file_path)
|
|
65
|
-
YAML.safe_load(
|
|
66
|
-
File.read(file_path, encoding: "UTF-8"),
|
|
67
|
-
aliases: true,
|
|
68
|
-
)
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
def load_manifest_files(collection_files)
|
|
72
|
-
manifest_files = collection_files.map do |c|
|
|
73
|
-
collection_data = load_yaml(c)
|
|
74
|
-
collection_data["manifest"]["docref"].map { |docref| docref["file"] }
|
|
75
|
-
end
|
|
76
|
-
manifest_files.flatten
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
def load_project_schemas( # rubocop:disable Metrics/AbcSize
|
|
80
|
-
manifest_files, exclude_paths, schema_manifest_file
|
|
81
|
-
)
|
|
82
|
-
all_schemas = { "schemas" => {} }
|
|
83
|
-
|
|
84
|
-
manifest_files.each do |file|
|
|
85
|
-
# load schemas.yaml from the location of the collection.yml file
|
|
86
|
-
schemas_file_path = File.expand_path(
|
|
87
|
-
file.gsub("collection.yml", "schemas.yaml"),
|
|
88
|
-
)
|
|
89
|
-
unless File.exist?(schemas_file_path)
|
|
90
|
-
puts "Schemas file not found: #{schemas_file_path}"
|
|
91
|
-
next
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
schemas_data = load_yaml(schemas_file_path)
|
|
95
|
-
|
|
96
|
-
if schemas_data["schemas"]
|
|
97
|
-
schemas_data["schemas"] = fix_path(
|
|
98
|
-
schemas_data,
|
|
99
|
-
schemas_file_path,
|
|
100
|
-
schema_manifest_file,
|
|
101
|
-
)
|
|
102
|
-
all_schemas["schemas"].merge!(schemas_data["schemas"])
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
if exclude_paths
|
|
106
|
-
all_schemas["schemas"].delete_if do |_key, value|
|
|
107
|
-
value["path"].match?(
|
|
108
|
-
Regexp.new(exclude_paths.gsub("*", "(.*){1,999}")),
|
|
109
|
-
)
|
|
110
|
-
end
|
|
111
|
-
end
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
all_schemas
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
def fix_path(schemas_data, schemas_file_path, schema_manifest_file) # rubocop:disable Metrics/AbcSize
|
|
118
|
-
schema_manifest_path = File.expand_path(schema_manifest_file, Dir.pwd)
|
|
119
|
-
|
|
120
|
-
schemas_data["schemas"].each do |key, value|
|
|
121
|
-
# resolve the path in the schema file by the path in the schemas.yaml
|
|
122
|
-
path_in_schema = File.expand_path(
|
|
123
|
-
value["path"],
|
|
124
|
-
File.dirname(schemas_file_path),
|
|
125
|
-
)
|
|
126
|
-
|
|
127
|
-
# calculate the relative path from the schema manifest file
|
|
128
|
-
fixed_path = Pathname.new(path_in_schema).relative_path_from(
|
|
129
|
-
Pathname.new(File.dirname(schema_manifest_path)),
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
{ key => value.merge!("path" => fixed_path.to_s) }
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
schemas_data["schemas"]
|
|
14
|
+
def generate_schemas(metanorma_manifest_file, schema_manifest_file)
|
|
15
|
+
SchemaManifestGenerator.new(
|
|
16
|
+
metanorma_manifest_file,
|
|
17
|
+
schema_manifest_file,
|
|
18
|
+
exclude_paths: options[:exclude_paths],
|
|
19
|
+
).generate
|
|
136
20
|
end
|
|
137
21
|
end
|
|
138
22
|
end
|
data/lib/suma/cli/reformat.rb
CHANGED
data/lib/suma/cli/validate.rb
CHANGED
|
@@ -9,8 +9,6 @@ module Suma
|
|
|
9
9
|
desc "links SCHEMAS_FILE DOCUMENTS_PATH [OUTPUT_FILE]",
|
|
10
10
|
"Extract and validate express links without creating intermediate file"
|
|
11
11
|
def links(*args)
|
|
12
|
-
require_relative "validate_links"
|
|
13
|
-
|
|
14
12
|
# Forward the command to ValidateLinks
|
|
15
13
|
links = Cli::ValidateLinks.new
|
|
16
14
|
links.extract_and_validate(*args)
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "thor"
|
|
4
|
-
require_relative "../utils"
|
|
5
4
|
require "expressir"
|
|
6
5
|
|
|
7
6
|
module Suma
|
|
8
7
|
module Cli
|
|
9
|
-
# ValidateLinks command for managing EXPRESS links
|
|
10
8
|
class ValidateLinks < Thor
|
|
11
9
|
desc "extract_and_validate SCHEMAS_FILE DOCUMENTS_PATH [OUTPUT_FILE]",
|
|
12
10
|
"Extract and validate express links without creating intermediate file"
|
|
@@ -16,47 +14,38 @@ module Suma
|
|
|
16
14
|
load_dependencies
|
|
17
15
|
paths = prepare_file_paths(schemas_file, documents_path, output_file)
|
|
18
16
|
|
|
19
|
-
# Load schemas and extract links
|
|
20
17
|
schemas_config = load_schemas_config(paths[:schemas_file])
|
|
21
|
-
exp_files = collect_schema_paths(schemas_config,
|
|
22
|
-
paths[:schemas_file_rel])
|
|
18
|
+
exp_files = collect_schema_paths(schemas_config, paths[:schemas_file_rel])
|
|
23
19
|
adoc_files = find_adoc_files(paths[:documents_path])
|
|
24
20
|
|
|
25
21
|
all_files = adoc_files + exp_files
|
|
26
22
|
display_file_counts(adoc_files, exp_files)
|
|
27
23
|
|
|
28
|
-
# Extract links from files
|
|
29
24
|
links_by_file = extract_links(all_files)
|
|
30
25
|
|
|
31
|
-
# Validate links against schemas
|
|
32
26
|
repo = load_express_schemas(schemas_config)
|
|
33
|
-
|
|
27
|
+
index = SchemaIndex.new(repo)
|
|
28
|
+
unresolved = LinkValidator.new(index).validate(links_by_file)
|
|
34
29
|
|
|
35
|
-
# Generate and output results
|
|
36
30
|
write_validation_results(paths[:output_file], paths[:output_file_rel],
|
|
37
|
-
|
|
31
|
+
unresolved, links_by_file)
|
|
38
32
|
end
|
|
39
33
|
|
|
40
34
|
private
|
|
41
35
|
|
|
42
|
-
# Load all required dependencies for link validation
|
|
43
36
|
def load_dependencies
|
|
44
|
-
# Lazy-load dependencies only when this command is actually used
|
|
45
37
|
require "expressir"
|
|
46
38
|
require "ruby-progressbar"
|
|
47
39
|
require "pathname"
|
|
48
40
|
end
|
|
49
41
|
|
|
50
|
-
# Prepare and normalize all file paths needed for the operation
|
|
51
42
|
def prepare_file_paths(schemas_file, documents_path, output_file)
|
|
52
|
-
# Convert to absolute paths
|
|
53
43
|
schemas_file_path = Pathname.new(schemas_file).expand_path
|
|
54
|
-
|
|
44
|
+
documents_path_exp = Pathname.new(documents_path).expand_path
|
|
55
45
|
output_file_path = Pathname.new(output_file).expand_path
|
|
56
46
|
|
|
57
|
-
# Store relative paths for display
|
|
58
47
|
schemas_file_rel = Pathname.new(schemas_file_path).relative_path_from(Pathname.pwd).to_s
|
|
59
|
-
documents_path_rel = Pathname.new(
|
|
48
|
+
documents_path_rel = Pathname.new(documents_path_exp).relative_path_from(Pathname.pwd).to_s
|
|
60
49
|
output_file_rel = Pathname.new(output_file_path).relative_path_from(Pathname.pwd).to_s
|
|
61
50
|
|
|
62
51
|
puts "Extracting and validating express links using schemas from #{schemas_file_rel}..."
|
|
@@ -65,46 +54,35 @@ module Suma
|
|
|
65
54
|
{
|
|
66
55
|
schemas_file: schemas_file_path,
|
|
67
56
|
schemas_file_rel: schemas_file_rel,
|
|
68
|
-
documents_path:
|
|
57
|
+
documents_path: documents_path_exp,
|
|
69
58
|
documents_path_rel: documents_path_rel,
|
|
70
59
|
output_file: output_file_path,
|
|
71
60
|
output_file_rel: output_file_rel,
|
|
72
61
|
}
|
|
73
62
|
end
|
|
74
63
|
|
|
75
|
-
# Load and initialize the schemas configuration
|
|
76
64
|
def load_schemas_config(schemas_file_path)
|
|
77
65
|
schemas_config = Expressir::SchemaManifest.from_yaml(File.read(schemas_file_path))
|
|
78
|
-
# Ensure the config is initialized with the correct path to resolve relative paths
|
|
79
66
|
schemas_config.set_initial_path(schemas_file_path.to_s)
|
|
80
67
|
schemas_config
|
|
81
68
|
rescue StandardError => e
|
|
82
|
-
|
|
83
|
-
exit(1)
|
|
69
|
+
raise Suma::Error, "Error loading schemas file: #{e.message}"
|
|
84
70
|
end
|
|
85
71
|
|
|
86
|
-
# Collect paths to all schema files from the config
|
|
87
72
|
def collect_schema_paths(schemas_config, schemas_file_rel)
|
|
88
|
-
exp_files =
|
|
89
|
-
schemas_config.schemas.each do |schema|
|
|
90
|
-
exp_files << schema.path if schema.path
|
|
91
|
-
end
|
|
92
|
-
|
|
73
|
+
exp_files = schemas_config.schemas.filter_map(&:path)
|
|
93
74
|
puts "Found #{exp_files.size} EXPRESS schema files from #{schemas_file_rel}"
|
|
94
75
|
exp_files
|
|
95
76
|
end
|
|
96
77
|
|
|
97
|
-
# Find all AsciiDoc files in the specified path
|
|
98
78
|
def find_adoc_files(documents_path)
|
|
99
79
|
Dir.glob(documents_path.join("**", "*.adoc").to_s)
|
|
100
80
|
end
|
|
101
81
|
|
|
102
|
-
# Display counts of discovered files
|
|
103
82
|
def display_file_counts(adoc_files, exp_files)
|
|
104
83
|
puts "Found #{adoc_files.size} AsciiDoc files and #{exp_files.size} EXPRESS files"
|
|
105
84
|
end
|
|
106
85
|
|
|
107
|
-
# Create a standardized progress bar
|
|
108
86
|
def create_progress_bar(title, total)
|
|
109
87
|
ProgressBar.create(
|
|
110
88
|
title: title,
|
|
@@ -116,7 +94,6 @@ module Suma
|
|
|
116
94
|
)
|
|
117
95
|
end
|
|
118
96
|
|
|
119
|
-
# Extract EXPRESS links from all files
|
|
120
97
|
def extract_links(files)
|
|
121
98
|
links_by_file = {}
|
|
122
99
|
link_count = 0
|
|
@@ -127,7 +104,6 @@ module Suma
|
|
|
127
104
|
progress.increment
|
|
128
105
|
begin
|
|
129
106
|
content = File.read(file)
|
|
130
|
-
# Extract links while ignoring text after comma
|
|
131
107
|
express_links = content.scan(/<<express:([^,>]+)(?:,[^>]+)?>>/).flatten.uniq
|
|
132
108
|
|
|
133
109
|
if express_links.any?
|
|
@@ -143,282 +119,31 @@ module Suma
|
|
|
143
119
|
links_by_file
|
|
144
120
|
end
|
|
145
121
|
|
|
146
|
-
# Load all EXPRESS schemas for validation
|
|
147
122
|
def load_express_schemas(schemas_config)
|
|
148
|
-
# Get all schema paths for validation
|
|
149
123
|
schema_paths = {}
|
|
150
|
-
schemas_config.schemas.each
|
|
151
|
-
schema_paths[schema.id] = schema.path
|
|
152
|
-
end
|
|
124
|
+
schemas_config.schemas.each { |s| schema_paths[s.id] = s.path }
|
|
153
125
|
|
|
154
126
|
puts "Loading #{schema_paths.size} EXPRESS schemas for validation..."
|
|
155
127
|
|
|
156
|
-
|
|
157
|
-
loading_progress = create_progress_bar("Loading schemas",
|
|
158
|
-
schema_paths.size)
|
|
128
|
+
loading_progress = create_progress_bar("Loading schemas", schema_paths.size)
|
|
159
129
|
|
|
160
|
-
# Try to load all schemas with progress tracking
|
|
161
130
|
begin
|
|
162
131
|
repo = Expressir::Express::Parser.from_files(schema_paths.values) do |filename, _schemas, error|
|
|
163
132
|
loading_progress.increment
|
|
164
|
-
if error
|
|
165
|
-
puts "\nWarning: Error loading schema #{filename}: #{error.message}"
|
|
166
|
-
end
|
|
133
|
+
puts "\nWarning: Error loading schema #{filename}: #{error.message}" if error
|
|
167
134
|
end
|
|
168
135
|
|
|
169
136
|
puts "Successfully loaded #{repo.schemas.size} schemas"
|
|
170
137
|
repo
|
|
171
138
|
rescue StandardError => e
|
|
172
|
-
|
|
173
|
-
exit(1)
|
|
174
|
-
end
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
# Validate all links against loaded schemas
|
|
178
|
-
def validate_links(links_by_file, repo)
|
|
179
|
-
unresolved_links = []
|
|
180
|
-
total_links = links_by_file.values.sum(&:size)
|
|
181
|
-
|
|
182
|
-
progress = create_progress_bar("Validating links", total_links)
|
|
183
|
-
|
|
184
|
-
links_by_file.each do |file, links|
|
|
185
|
-
validate_file_links(file, links, repo, progress, unresolved_links)
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
unresolved_links
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
# Validate links in a specific file
|
|
192
|
-
def validate_file_links(file, links, repo, progress, unresolved_links)
|
|
193
|
-
file_content = File.read(file)
|
|
194
|
-
file_lines = file_content.lines
|
|
195
|
-
|
|
196
|
-
links.each do |link|
|
|
197
|
-
progress.increment
|
|
198
|
-
line_idx = find_link_line(file_lines, link)
|
|
199
|
-
next unless line_idx
|
|
200
|
-
|
|
201
|
-
# Parse link (schema only, schema.element, or schema.element.path)
|
|
202
|
-
parts = link.split(".")
|
|
203
|
-
|
|
204
|
-
if parts.size == 1
|
|
205
|
-
validate_schema_only_link(parts[0], repo, file, line_idx, link,
|
|
206
|
-
unresolved_links)
|
|
207
|
-
else
|
|
208
|
-
validate_schema_element_link(parts, repo, file, line_idx, link,
|
|
209
|
-
unresolved_links)
|
|
210
|
-
end
|
|
211
|
-
end
|
|
212
|
-
rescue StandardError => e
|
|
213
|
-
puts "Warning: Error processing file #{file}: #{e.message}"
|
|
214
|
-
end
|
|
215
|
-
|
|
216
|
-
# Find the line where a link appears in the file
|
|
217
|
-
def find_link_line(file_lines, link)
|
|
218
|
-
file_lines.each_with_index do |line, idx|
|
|
219
|
-
# Match both with and without comma text
|
|
220
|
-
if /<<express:#{Regexp.escape(link)}(?:,[^>]+)?>>/.match?(line)
|
|
221
|
-
return idx
|
|
222
|
-
end
|
|
223
|
-
end
|
|
224
|
-
nil
|
|
225
|
-
end
|
|
226
|
-
|
|
227
|
-
# Validate a link that only references a schema
|
|
228
|
-
def validate_schema_only_link(schema_name, repo, file, line_idx, link,
|
|
229
|
-
unresolved_links)
|
|
230
|
-
# Check if schema exists
|
|
231
|
-
schema = repo.schemas.find do |s|
|
|
232
|
-
s.id.downcase == schema_name.downcase
|
|
233
|
-
end
|
|
234
|
-
|
|
235
|
-
if !schema
|
|
236
|
-
unresolved_links << {
|
|
237
|
-
file: file,
|
|
238
|
-
line: line_idx + 1,
|
|
239
|
-
link: link,
|
|
240
|
-
reason: "Schema '#{schema_name}' not found",
|
|
241
|
-
}
|
|
242
|
-
end
|
|
243
|
-
end
|
|
244
|
-
|
|
245
|
-
# Validate a link with schema.element or deeper paths
|
|
246
|
-
def validate_schema_element_link(parts, repo, file, line_idx, link,
|
|
247
|
-
unresolved_links)
|
|
248
|
-
schema_name = parts[0]
|
|
249
|
-
element_name = parts[1]
|
|
250
|
-
|
|
251
|
-
# Check if schema exists
|
|
252
|
-
schema = repo.schemas.find do |s|
|
|
253
|
-
s.id.downcase == schema_name.downcase
|
|
254
|
-
end
|
|
255
|
-
|
|
256
|
-
if !schema
|
|
257
|
-
unresolved_links << {
|
|
258
|
-
file: file,
|
|
259
|
-
line: line_idx + 1,
|
|
260
|
-
link: link,
|
|
261
|
-
reason: "Schema '#{schema_name}' not found",
|
|
262
|
-
}
|
|
263
|
-
return
|
|
264
|
-
end
|
|
265
|
-
|
|
266
|
-
# Find the element in the schema
|
|
267
|
-
element = find_schema_element(schema, element_name)
|
|
268
|
-
|
|
269
|
-
if !element
|
|
270
|
-
unresolved_links << {
|
|
271
|
-
file: file,
|
|
272
|
-
line: line_idx + 1,
|
|
273
|
-
link: link,
|
|
274
|
-
reason: "Element '#{element_name}' not found in schema '#{schema_name}'",
|
|
275
|
-
}
|
|
276
|
-
return
|
|
277
|
-
end
|
|
278
|
-
|
|
279
|
-
# If we have more than 2 parts, validate the deeper path
|
|
280
|
-
if parts.size > 2
|
|
281
|
-
validation_error = validate_deep_path(schema, element, parts[2..],
|
|
282
|
-
file, line_idx, link)
|
|
283
|
-
unresolved_links << validation_error if validation_error
|
|
284
|
-
end
|
|
285
|
-
end
|
|
286
|
-
|
|
287
|
-
# Find an element in a schema by name
|
|
288
|
-
def find_schema_element(schema, element_name)
|
|
289
|
-
# Try to find element in various collections
|
|
290
|
-
element_collections = [
|
|
291
|
-
schema.entities,
|
|
292
|
-
schema.types,
|
|
293
|
-
schema.constants,
|
|
294
|
-
schema.functions,
|
|
295
|
-
schema.rules,
|
|
296
|
-
schema.procedures,
|
|
297
|
-
schema.subtype_constraints,
|
|
298
|
-
]
|
|
299
|
-
|
|
300
|
-
element_collections.each do |collection|
|
|
301
|
-
element = collection&.find do |e|
|
|
302
|
-
e.id.downcase == element_name.downcase
|
|
303
|
-
end
|
|
304
|
-
return element if element
|
|
305
|
-
end
|
|
306
|
-
|
|
307
|
-
nil
|
|
308
|
-
end
|
|
309
|
-
|
|
310
|
-
# Validate deeper paths in a link (schema.element.path.subpath)
|
|
311
|
-
def validate_deep_path(schema, element, path_parts, file, line_idx,
|
|
312
|
-
full_link)
|
|
313
|
-
current_element = element
|
|
314
|
-
current_path = "#{schema.id}.#{element.id}"
|
|
315
|
-
|
|
316
|
-
# Process each part of the path
|
|
317
|
-
path_parts.each do |part|
|
|
318
|
-
# The validation logic depends on the type of current element
|
|
319
|
-
case current_element
|
|
320
|
-
when Expressir::Express::Entity
|
|
321
|
-
# For entities, check attributes
|
|
322
|
-
attribute = current_element.attributes&.find do |a|
|
|
323
|
-
a.id.downcase == part.downcase
|
|
324
|
-
end
|
|
325
|
-
|
|
326
|
-
unless attribute
|
|
327
|
-
return {
|
|
328
|
-
file: file,
|
|
329
|
-
line: line_idx + 1,
|
|
330
|
-
link: full_link,
|
|
331
|
-
reason: "Attribute '#{part}' not found in entity '#{current_path}'",
|
|
332
|
-
}
|
|
333
|
-
end
|
|
334
|
-
|
|
335
|
-
current_element = attribute
|
|
336
|
-
current_path += ".#{part}"
|
|
337
|
-
|
|
338
|
-
when Expressir::Express::Type
|
|
339
|
-
# For types, validation depends on type kind
|
|
340
|
-
if current_element.respond_to?(:base_type) && current_element.base_type
|
|
341
|
-
# For derived types, find the base type
|
|
342
|
-
base_type = find_base_type(schema, current_element.base_type)
|
|
343
|
-
|
|
344
|
-
unless base_type
|
|
345
|
-
return {
|
|
346
|
-
file: file,
|
|
347
|
-
line: line_idx + 1,
|
|
348
|
-
link: full_link,
|
|
349
|
-
reason: "Base type not found for '#{current_path}'",
|
|
350
|
-
}
|
|
351
|
-
end
|
|
352
|
-
|
|
353
|
-
# Continue validation using base type
|
|
354
|
-
current_element = base_type
|
|
355
|
-
|
|
356
|
-
elsif current_element.respond_to?(:elements) && current_element.elements
|
|
357
|
-
# For enum types, check enum values
|
|
358
|
-
enum_value = current_element.elements.find do |e|
|
|
359
|
-
e.id.downcase == part.downcase
|
|
360
|
-
end
|
|
361
|
-
|
|
362
|
-
unless enum_value
|
|
363
|
-
return {
|
|
364
|
-
file: file,
|
|
365
|
-
line: line_idx + 1,
|
|
366
|
-
link: full_link,
|
|
367
|
-
reason: "Enumeration value '#{part}' not found in type '#{current_path}'",
|
|
368
|
-
}
|
|
369
|
-
end
|
|
370
|
-
|
|
371
|
-
current_element = enum_value
|
|
372
|
-
current_path += ".#{part}"
|
|
373
|
-
|
|
374
|
-
else
|
|
375
|
-
# For other types, we can't navigate deeper
|
|
376
|
-
return {
|
|
377
|
-
file: file,
|
|
378
|
-
line: line_idx + 1,
|
|
379
|
-
link: full_link,
|
|
380
|
-
reason: "Cannot navigate deeper from type '#{current_path}'",
|
|
381
|
-
}
|
|
382
|
-
end
|
|
383
|
-
|
|
384
|
-
else
|
|
385
|
-
# For other element types, navigation is not supported
|
|
386
|
-
return {
|
|
387
|
-
file: file,
|
|
388
|
-
line: line_idx + 1,
|
|
389
|
-
link: full_link,
|
|
390
|
-
reason: "Cannot navigate deeper from '#{current_path}'",
|
|
391
|
-
}
|
|
392
|
-
end
|
|
393
|
-
end
|
|
394
|
-
|
|
395
|
-
# If we've processed all parts without returning an error, path is valid
|
|
396
|
-
nil
|
|
397
|
-
end
|
|
398
|
-
|
|
399
|
-
# Find a base type in a schema
|
|
400
|
-
def find_base_type(schema, type_ref)
|
|
401
|
-
# Skip built-in types
|
|
402
|
-
return nil if %w[INTEGER REAL STRING BOOLEAN NUMBER BINARY
|
|
403
|
-
LOGICAL].include?(type_ref.to_s.upcase)
|
|
404
|
-
|
|
405
|
-
# Find the referenced type in the schema
|
|
406
|
-
if type_ref.is_a?(String)
|
|
407
|
-
find_schema_element(schema, type_ref)
|
|
408
|
-
elsif type_ref.respond_to?(:id)
|
|
409
|
-
# It's already a type object
|
|
410
|
-
type_ref
|
|
411
|
-
else
|
|
412
|
-
nil
|
|
139
|
+
raise Suma::Error, "Error loading schemas: #{e.message}"
|
|
413
140
|
end
|
|
414
141
|
end
|
|
415
142
|
|
|
416
|
-
# Write validation results to the output file
|
|
417
143
|
def write_validation_results(output_file_path, output_file_rel,
|
|
418
144
|
unresolved_links, links_by_file)
|
|
419
145
|
total_links = links_by_file.values.sum(&:size)
|
|
420
146
|
|
|
421
|
-
# Prepare results for output
|
|
422
147
|
results = []
|
|
423
148
|
results << "Validation complete. Checked #{total_links} links."
|
|
424
149
|
|
|
@@ -427,17 +152,15 @@ module Suma
|
|
|
427
152
|
else
|
|
428
153
|
results << "❌ Found #{unresolved_links.size} unresolved links:"
|
|
429
154
|
unresolved_links.each do |issue|
|
|
430
|
-
results << "#{issue
|
|
155
|
+
results << "#{issue.file}:#{issue.line} - <<express:#{issue.link}>> - #{issue.reason}"
|
|
431
156
|
end
|
|
432
157
|
end
|
|
433
158
|
|
|
434
|
-
# Write results to output file
|
|
435
159
|
begin
|
|
436
160
|
File.write(output_file_path, results.join("\n"))
|
|
437
161
|
puts "Validation results written to #{output_file_rel}"
|
|
438
162
|
rescue StandardError => e
|
|
439
163
|
puts "Error writing to output file: #{e.message}"
|
|
440
|
-
# Still print results to console as fallback
|
|
441
164
|
puts results
|
|
442
165
|
end
|
|
443
166
|
end
|