edoxen 0.3.0 → 0.3.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c2fe56ab6ddff9606df4af319cba2d372d7114bce66822968710cb15cf0886cd
4
- data.tar.gz: 7e30d4153daaa03d9cab2d10170947b10c646526bcc6b156ef909a5b8557d61d
3
+ metadata.gz: a87eaf24182b1fd7400efbd2af10681f4059a35cd57c807759a8015a59a1c0f1
4
+ data.tar.gz: 53ede8ad87d1a25bbf766d7196e6437a8455f431af313ba5b9752cbd42d17050
5
5
  SHA512:
6
- metadata.gz: 5665a5650d5ac075b1905095578a188d5ce4e1a94d3fac522133055d14bdc356a5f49382256f9ea3f9b7c159bc7c2d5dc5315b1fa7c5354ec8a016d2cd85f3c3
7
- data.tar.gz: 6c269809b5e14f084d1a427a68a7e623466550dde648ffad9add85fae84ae5b1607de77adf70bfa5f0a90a6f04cfd4db842f0287c84d9e6120286a2eb4486d94
6
+ metadata.gz: a14ac6664ffa8ec1f1fcd31ace23257a740dcad3fab41ead54d542440a53618ac8ad46bb495afeb44c62db7b850d35d789ee7a888245f4e6b3ac0afe092310ee
7
+ data.tar.gz: 991a18091cb7ec21ddfe70b2691244b3e8a6ba869135545f6249ffa35d56439afbf578a867d56bdce6c3555f177c59f43c73f8c27abb119527af36c42e6b17fd
data/CLAUDE.md CHANGED
@@ -93,9 +93,13 @@ that propagate:
93
93
 
94
94
  `lib/edoxen/schema_validator.rb` is intentionally small:
95
95
 
96
- - `SchemaValidator::ValidationError` rendered as `file:line:col: msg`.
97
- - `#validate_file(path)` `Array<ValidationError>`.
98
- - `#validate_content(content, path)` `Array<ValidationError>`.
96
+ - `Edoxen::ValidationError` (defined in `lib/edoxen/error.rb`) — the unified
97
+ validation shape. `SchemaValidator::ValidationError` is a back-compat
98
+ alias. Carries `file`, `line`, `column`, `pointer`, `message_text`, and
99
+ `source` (`:schema` / `:model` / `:syntax`) so renderers can branch on
100
+ failure origin. Rendered as `file:line:col: msg at \`/pointer\``.
101
+ - `#validate_file(path)` → `Array<Edoxen::ValidationError>`.
102
+ - `#validate_content(content, path)` → `Array<Edoxen::ValidationError>`.
99
103
  - `SchemaValidator::LineMap` — builds an indent-heuristic
100
104
  `{json_pointer => line_no}` map and resolves a JSON-Schema data
101
105
  pointer to a line via longest-prefix match. **No path-shape
@@ -119,6 +123,16 @@ that propagate:
119
123
  result back. Preserves any `# yaml-language-server: $schema=...`
120
124
  directive in the first 5 lines.
121
125
 
126
+ Both commands delegate their expand/sort/empty/header/loop/tally/summary/exit
127
+ scaffold to `Edoxen::Cli::Batch` — the deep module behind the seam. The
128
+ command bodies are per-file blocks returning `Batch::Result.ok(msg)` or
129
+ `Batch::Result.bad(errors)`. Adding a third command (e.g. `lint`, `diff`)
130
+ is one block.
131
+
132
+ `Resolution#in_language(code, fallback:)` and `Resolution#primary_localization`
133
+ provide the canonical lookup interface — callers should not iterate
134
+ `localizations.find { |l| l.language_code == code }` directly.
135
+
122
136
  Exit code is non-zero if any file fails.
123
137
 
124
138
  ## Build, test, lint
@@ -139,15 +153,15 @@ mkdir -p /tmp/out && bundle exec exe/edoxen normalize "spec/fixtures/*.yaml" --o
139
153
 
140
154
  - **No `double()` in specs** — use real `Edoxen::*` instances and fixture files.
141
155
  - **Serialization is framework-only.** Never hand-roll `to_h` / `from_h` /
142
- `to_yaml` / `from_yaml` / `to_json` / `from_json` on a model class. Use
143
- `attribute` + `key_value do end` and let lutaml-model do it. Renames
144
- are `map "wire_name", to: :attr` never key-swapping inside a method.
156
+ `to_yaml` / `from_yaml` / `to_json` / `from_json` on a model class. Declare
157
+ `attribute` only lutaml-model auto-emits an identity map for each
158
+ attribute when no `key_value` block is present. Add a `key_value do … end`
159
+ block with `map "wire_name", to: :attr` only when the wire name differs
160
+ from the attribute name.
145
161
  - **Wire names are `snake_case`.** Even when the LUTAML notation uses
146
162
  camelCase, the YAML wire form is `snake_case` (`agenda_item`, not
147
- `agendaItem`). The attribute name on the Ruby side is also `snake_case`;
148
- when they match exactly, drop the explicit `map` declaration only after
149
- checking lutaml-model supports it. (Today lutaml-model needs an explicit
150
- `map` for `key_value`; do not delete them.)
163
+ `agendaItem`). The attribute name on the Ruby side is also `snake_case`,
164
+ and the wire name defaults to it.
151
165
  - **Enums on the model and the schema must agree.** When you add or remove
152
166
  a value to `Enums::*`, also update the matching `enum:` list in
153
167
  `schema/edoxen.yaml` $defs. The `schema_enum_sync_spec` will catch any
data/lib/edoxen/action.rb CHANGED
@@ -7,11 +7,5 @@ module Edoxen
7
7
  attribute :type, :string, values: Enums::ACTION_TYPE
8
8
  attribute :date_effective, ResolutionDate
9
9
  attribute :message, :string
10
-
11
- key_value do
12
- map "type", to: :type
13
- map "date_effective", to: :date_effective
14
- map "message", to: :message
15
- end
16
10
  end
17
11
  end
@@ -8,12 +8,5 @@ module Edoxen
8
8
  attribute :degree, :string, values: Enums::APPROVAL_DEGREE
9
9
  attribute :date, ResolutionDate
10
10
  attribute :message, :string
11
-
12
- key_value do
13
- map "type", to: :type
14
- map "degree", to: :degree
15
- map "date", to: :date
16
- map "message", to: :message
17
- end
18
11
  end
19
12
  end
data/lib/edoxen/cli.rb CHANGED
@@ -4,54 +4,101 @@ require "thor"
4
4
  require "fileutils"
5
5
 
6
6
  module Edoxen
7
- # Thor command-line surface for the gem. Two responsibilities:
8
- # * `validate PATTERN` — runs both JSON-Schema validation and the
9
- # Ruby model parser against each matching YAML file.
10
- # * `normalize PATTERN (--output DIR | --inplace)` — round-trips each
11
- # matching YAML file through the Ruby model, preserving any
12
- # `# yaml-language-server: $schema=...` directive on the first line.
13
- #
14
- # The CLI deliberately does NOT own schema-or-model decisions — those
15
- # live in `SchemaValidator` and `Lutaml::Model` respectively. It only
16
- # glues them together and formats output.
17
7
  class Cli < Thor
18
- package_name "edoxen"
8
+ # Deep module behind the per-command interface. Owns the
9
+ # expand/sort/empty/header/loop/tally/summary/exit scaffold so
10
+ # `validate` and `normalize` collapse to per-file blocks.
11
+ #
12
+ # Commands call `Batch.run(self, pattern, header:)` and yield a block
13
+ # that returns `Result.ok(message)` or `Result.bad(errors)`. The
14
+ # batch runner prints progress, tallies, prints the summary, and
15
+ # exits with the right code.
16
+ #
17
+ # In-process; no adapter. The seam is the call site in each
18
+ # command method — internal to the CLI.
19
+ module Batch
20
+ # Per-file outcome. `ok` carries an optional message appended to
21
+ # the success indicator (e.g. "NORMALIZED → /out/path"). `bad`
22
+ # carries a list of pre-formatted error strings.
23
+ Result = Struct.new(:status, :message, :errors) do
24
+ def self.ok(message = nil)
25
+ new(:ok, message, nil)
26
+ end
19
27
 
20
- desc "validate YAML_FILE_PATTERN",
21
- "Validate one or more Edoxen YAML files against the schema and the model."
28
+ def self.bad(errors)
29
+ new(:bad, nil, Array(errors))
30
+ end
31
+ end
22
32
 
23
- def validate(pattern)
24
- files = expand_yaml_pattern(pattern)
25
- if files.empty?
26
- say "No files found matching pattern: #{pattern}", :red
27
- exit 1
33
+ module_function
34
+
35
+ # Run a batch over every YAML file matching `pattern`.
36
+ #
37
+ # Yields each file path to the caller's block. Block must return
38
+ # a Batch::Result. The batch runner handles progress output,
39
+ # tallies, summary, and the exit code.
40
+ def run(cli, pattern, header:, summary_extra: [])
41
+ files = expand(pattern)
42
+ if files.empty?
43
+ cli.say "No files found matching pattern: #{pattern}", :red
44
+ exit 1
45
+ end
46
+
47
+ cli.say "#{header} #{files.size} file(s)...", :blue
48
+
49
+ ok_count = 0
50
+ bad_count = 0
51
+
52
+ files.each do |file|
53
+ $stdout.print " #{File.basename(file)}... "
54
+ result = yield file
55
+ if result.status == :ok
56
+ label = result.message ? "✅ #{result.message}" : "✅"
57
+ cli.say label, :green
58
+ ok_count += 1
59
+ else
60
+ cli.say "❌ INVALID", :red
61
+ bad_count += 1
62
+ Array(result.errors).each { |e| cli.say " #{e}", :red }
63
+ end
64
+ end
65
+
66
+ print_summary(cli, files.size, ok_count, bad_count, summary_extra)
67
+ exit(bad_count.positive? ? 1 : 0)
28
68
  end
29
69
 
30
- say "🔍 Validating #{files.size} file(s)...", :blue
70
+ def expand(pattern)
71
+ Dir.glob(pattern).select { |f| File.file?(f) && f.match?(/\.ya?ml\z/i) }.sort
72
+ end
31
73
 
32
- validator = SchemaValidator.new
33
- valid_count = 0
34
- invalid_count = 0
74
+ def print_summary(cli, total, ok_count, bad_count, summary_extra)
75
+ cli.say "\n📊 Summary:", :blue
76
+ cli.say " Total: #{total}", :blue
77
+ cli.say " Success: #{ok_count}, Failed: #{bad_count}",
78
+ bad_count.positive? ? :red : :green
79
+ rate = total.zero? ? 0 : ((ok_count.to_f / total) * 100).round(1)
80
+ cli.say " Success rate: #{rate}%", :blue
81
+ summary_extra.each { |label, value| cli.say " #{label}: #{value}", :blue }
82
+ end
83
+ end
35
84
 
36
- files.each do |file|
37
- print " #{File.basename(file)}... "
85
+ package_name "edoxen"
38
86
 
87
+ desc "validate YAML_FILE_PATTERN",
88
+ "Validate one or more Edoxen YAML files against the schema and the model."
89
+
90
+ def validate(pattern)
91
+ validator = SchemaValidator.new
92
+ Batch.run(self, pattern, header: "🔍 Validating") do |file|
39
93
  schema_errors = validator.validate_file(file)
40
94
  model_errors = collect_model_errors(file)
41
-
42
95
  if schema_errors.empty? && model_errors.empty?
43
- say "VALID", :green
44
- valid_count += 1
96
+ Batch::Result.ok("VALID")
45
97
  else
46
- say "❌ INVALID", :red
47
- invalid_count += 1
48
- schema_errors.each { |e| say " #{e.to_clickable_format}", :red }
49
- model_errors.each { |m| say " #{file}:1:1: #{m}", :red }
98
+ errors = (schema_errors + model_errors).map(&:to_clickable_format)
99
+ Batch::Result.bad(errors)
50
100
  end
51
101
  end
52
-
53
- print_summary(files.size, valid_count, invalid_count, validator_type: :binary)
54
- exit(invalid_count.positive? ? 1 : 0)
55
102
  end
56
103
 
57
104
  desc "normalize YAML_FILE_PATTERN",
@@ -61,77 +108,34 @@ module Edoxen
61
108
  option :inplace, type: :boolean, desc: "Modify files in place (no backup)"
62
109
 
63
110
  def normalize(pattern)
64
- if options[:output] && options[:inplace]
65
- say "Error: Cannot use both --output and --inplace options", :red
111
+ unless valid_normalize_options?
112
+ say normalize_options_error, :red
66
113
  exit 1
67
114
  end
68
115
 
69
- unless options[:output] || options[:inplace]
70
- say "Error: Must specify either --output or --inplace option", :red
71
- exit 1
72
- end
73
-
74
- files = expand_yaml_pattern(pattern)
75
- if files.empty?
76
- say "No files found matching pattern: #{pattern}", :red
77
- exit 1
78
- end
116
+ summary_extra = [
117
+ [" Output directory", options[:output]],
118
+ [" Mode", options[:inplace] ? "in place" : "--output"]
119
+ ].compact
79
120
 
80
- say "🔄 Normalizing #{files.size} file(s)...", :blue
81
-
82
- success_count = 0
83
- error_count = 0
84
-
85
- files.each do |file|
86
- print " #{File.basename(file)}... "
87
- begin
88
- yaml_language_server_comment = extract_yaml_language_server_comment(File.read(file))
89
- normalized = ResolutionCollection.from_yaml(File.read(file)).to_yaml
90
- normalized = "#{yaml_language_server_comment}\n#{normalized}" if yaml_language_server_comment
91
-
92
- if options[:inplace]
93
- File.write(file, normalized)
94
- say "✅ NORMALIZED", :green
95
- else
96
- out = File.join(options[:output], File.basename(file))
97
- FileUtils.mkdir_p(File.dirname(out))
98
- File.write(out, normalized)
99
- say "✅ NORMALIZED → #{out}", :green
100
- end
101
- success_count += 1
102
- rescue StandardError => e
103
- say "❌ FAILED — #{e.message}", :red
104
- error_count += 1
105
- end
121
+ Batch.run(self, pattern, header: "🔄 Normalizing", summary_extra: summary_extra) do |file|
122
+ Batch::Result.ok(normalize_file(file))
123
+ rescue StandardError => e
124
+ Batch::Result.bad(["#{file}:1:1: #{e.message}"])
106
125
  end
107
-
108
- print_summary(files.size, success_count, error_count, validator_type: :lax,
109
- extra: [
110
- [" Output directory", options[:output]],
111
- [" Mode", options[:inplace] ? "in place" : "--output"]
112
- ].compact)
113
- exit(error_count.positive? ? 1 : 0)
114
- end
115
-
116
- no_commands do
117
- # Reserved for private Thor plumbing if we add it later.
118
126
  end
119
127
 
120
128
  private
121
129
 
122
- def expand_yaml_pattern(pattern)
123
- Dir.glob(pattern).select { |f| File.file?(f) && f.match?(/\.ya?ml\z/i) }.sort
124
- end
125
-
126
- # Round-trip the file through the model to catch structural issues
127
- # (missing nested classes, type mismatches) that the JSON-Schema can't
128
- # express. The model is permissive about field names — schema is the
129
- # strict source.
130
130
  def collect_model_errors(file)
131
131
  ResolutionCollection.from_yaml(File.read(file))
132
132
  []
133
133
  rescue StandardError => e
134
- ["Model parsing failed: #{e.message}"]
134
+ [Edoxen::ValidationError.new(
135
+ file: file, line: 1, column: 1,
136
+ message_text: "Model parsing failed: #{e.message}",
137
+ source: Edoxen::ValidationError::SOURCE_MODEL
138
+ )]
135
139
  end
136
140
 
137
141
  def extract_yaml_language_server_comment(content)
@@ -139,18 +143,39 @@ module Edoxen
139
143
  lines.find { |l| l.strip.match?(/\A#\s*yaml-language-server:\s*\$schema=/) }&.rstrip
140
144
  end
141
145
 
142
- def print_summary(total, ok_count, bad_count, validator_type:, extra: [])
143
- say "\n📊 Summary:", :blue
144
- say " Total: #{total}", :blue
145
- label_text = if validator_type == :binary
146
- " Valid: #{ok_count}, Invalid: #{bad_count}"
147
- else
148
- " Success: #{ok_count}, Failed: #{bad_count}"
149
- end
150
- say label_text, bad_count.positive? ? :red : :green
151
- success_rate = total.zero? ? 0 : ((ok_count.to_f / total) * 100).round(1)
152
- say " Success rate: #{success_rate}%", :blue
153
- extra.each { |label, value| say " #{label}: #{value}", :blue }
146
+ def valid_normalize_options?
147
+ return false if options[:output] && options[:inplace]
148
+ return false unless options[:output] || options[:inplace]
149
+
150
+ true
151
+ end
152
+
153
+ def normalize_options_error
154
+ if options[:output] && options[:inplace]
155
+ "Error: Cannot use both --output and --inplace options"
156
+ else
157
+ "Error: Must specify either --output or --inplace option"
158
+ end
159
+ end
160
+
161
+ # Writes the normalized YAML either to the original file (--inplace)
162
+ # or under the --output directory. Returns a one-line status message
163
+ # for the batch runner to print after the ✅.
164
+ def normalize_file(file)
165
+ original = File.read(file)
166
+ yaml_language_server_comment = extract_yaml_language_server_comment(original)
167
+ normalized = ResolutionCollection.from_yaml(original).to_yaml
168
+ normalized = "#{yaml_language_server_comment}\n#{normalized}" if yaml_language_server_comment
169
+
170
+ if options[:inplace]
171
+ File.write(file, normalized)
172
+ "NORMALIZED"
173
+ else
174
+ out = File.join(options[:output], File.basename(file))
175
+ FileUtils.mkdir_p(File.dirname(out))
176
+ File.write(out, normalized)
177
+ "NORMALIZED → #{out}"
178
+ end
154
179
  end
155
180
  end
156
181
  end
@@ -7,11 +7,5 @@ module Edoxen
7
7
  attribute :type, :string, values: Enums::CONSIDERATION_TYPE
8
8
  attribute :date_effective, ResolutionDate
9
9
  attribute :message, :string
10
-
11
- key_value do
12
- map "type", to: :type
13
- map "date_effective", to: :date_effective
14
- map "message", to: :message
15
- end
16
10
  end
17
11
  end
data/lib/edoxen/error.rb CHANGED
@@ -1,7 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Edoxen
4
- # Base class for any Edoxen-level error. Reserved for raise-on-failure paths;
5
- # schema validation errors live under SchemaValidator::ValidationError.
4
+ # Base class for any Edoxen-level error. Reserved for raise-on-failure paths.
6
5
  class Error < StandardError; end
6
+
7
+ # Unified validation failure shape. Produced by both:
8
+ # * SchemaValidator — JSON-Schema violations and YAML syntax errors
9
+ # carry source: :schema (or :syntax for Psych::SyntaxError).
10
+ # * ResolutionCollection.from_yaml rescues in the CLI — model parse
11
+ # failures carry source: :model.
12
+ #
13
+ # One type at the seam means callers (CLI, future renderers, tests)
14
+ # handle one shape instead of N.
15
+ class ValidationError < Error
16
+ SOURCE_SCHEMA = :schema
17
+ SOURCE_MODEL = :model
18
+ SOURCE_SYNTAX = :syntax
19
+
20
+ attr_reader :file, :line, :column, :pointer, :message_text, :source
21
+
22
+ def initialize(file:, line:, column:, message_text:, pointer: "", source: SOURCE_SCHEMA)
23
+ @file = file
24
+ @line = line
25
+ @column = column
26
+ @pointer = pointer.to_s
27
+ @message_text = message_text
28
+ @source = source
29
+ super(to_clickable_format)
30
+ end
31
+
32
+ def to_clickable_format
33
+ suffix = @pointer.empty? ? "" : " at `#{@pointer}`"
34
+ "#{@file}:#{@line}:#{@column}: #{@message_text}#{suffix}"
35
+ end
36
+ end
7
37
  end
@@ -14,17 +14,5 @@ module Edoxen
14
14
  attribute :considerations, Consideration, collection: true
15
15
  attribute :approvals, Approval, collection: true
16
16
  attribute :actions, Action, collection: true
17
-
18
- key_value do
19
- map "language_code", to: :language_code
20
- map "script", to: :script
21
- map "title", to: :title
22
- map "subject", to: :subject
23
- map "message", to: :message
24
- map "considering", to: :considering
25
- map "considerations", to: :considerations
26
- map "approvals", to: :approvals
27
- map "actions", to: :actions
28
- end
29
17
  end
30
18
  end
@@ -6,10 +6,5 @@ module Edoxen
6
6
  class MeetingIdentifier < Lutaml::Model::Serializable
7
7
  attribute :venue, :string
8
8
  attribute :date, :date
9
-
10
- key_value do
11
- map "venue", to: :venue
12
- map "date", to: :date
13
- end
14
9
  end
15
10
  end
@@ -4,6 +4,11 @@ module Edoxen
4
4
  # A formal Resolution. Language-agnostic admin fields live here; every
5
5
  # translatable field is wrapped inside `localizations[]` (one entry per
6
6
  # available language; at least one is required by the schema).
7
+ #
8
+ # Wire names follow lutaml-model's default convention: each declared
9
+ # attribute serializes to its snake_case name on the wire. Override
10
+ # with an explicit `key_value do; map "wire", to: :attr; end` block
11
+ # only when the wire name differs.
7
12
  class Resolution < Lutaml::Model::Serializable
8
13
  attribute :identifier, StructuredIdentifier, collection: true
9
14
  attribute :type, :string, values: Enums::RESOLUTION_TYPE
@@ -17,18 +22,22 @@ module Edoxen
17
22
  attribute :urls, Url, collection: true
18
23
  attribute :localizations, Localization, collection: true
19
24
 
20
- key_value do
21
- map "identifier", to: :identifier
22
- map "type", to: :type
23
- map "doi", to: :doi
24
- map "urn", to: :urn
25
- map "agenda_item", to: :agenda_item
26
- map "dates", to: :dates
27
- map "categories", to: :categories
28
- map "meeting", to: :meeting
29
- map "relations", to: :relations
30
- map "urls", to: :urls
31
- map "localizations", to: :localizations
25
+ # Lookup by ISO 639-3 language code. Returns nil when no exact match
26
+ # exists and `fallback:` is false (the default); returns the first
27
+ # localization otherwise. Keeps the language-preference policy in
28
+ # one place so callers stop reimplementing `find { |l| ... }`.
29
+ def in_language(code, fallback: false)
30
+ match = localizations&.find { |loc| loc.language_code == code.to_s }
31
+ return match if match
32
+
33
+ fallback ? localizations&.first : nil
34
+ end
35
+
36
+ # The canonical rendering — English when available, else the first
37
+ # declared localization. Mirrors the glossarist LocalizedConcept
38
+ # "preferred language" notion.
39
+ def primary_localization
40
+ in_language("eng", fallback: true)
32
41
  end
33
42
  end
34
43
  end
@@ -6,10 +6,5 @@ module Edoxen
6
6
  class ResolutionCollection < Lutaml::Model::Serializable
7
7
  attribute :metadata, ResolutionMetadata
8
8
  attribute :resolutions, Resolution, collection: true
9
-
10
- key_value do
11
- map "metadata", to: :metadata
12
- map "resolutions", to: :resolutions
13
- end
14
9
  end
15
10
  end
@@ -7,10 +7,5 @@ module Edoxen
7
7
  class ResolutionDate < Lutaml::Model::Serializable
8
8
  attribute :date, :date
9
9
  attribute :type, :string, values: Enums::RESOLUTION_DATE_TYPE
10
-
11
- key_value do
12
- map "date", to: :date
13
- map "type", to: :type
14
- end
15
10
  end
16
11
  end
@@ -12,15 +12,5 @@ module Edoxen
12
12
  attribute :source_urls, SourceUrl, collection: true
13
13
  attribute :city, :string
14
14
  attribute :country_code, :string
15
-
16
- key_value do
17
- map "title", to: :title
18
- map "title_localized", to: :title_localized
19
- map "date", to: :date
20
- map "source", to: :source
21
- map "source_urls", to: :source_urls
22
- map "city", to: :city
23
- map "country_code", to: :country_code
24
- end
25
15
  end
26
16
  end
@@ -7,11 +7,5 @@ module Edoxen
7
7
  attribute :source, StructuredIdentifier
8
8
  attribute :destination, StructuredIdentifier
9
9
  attribute :type, :string, values: Enums::RESOLUTION_RELATION_TYPE
10
-
11
- key_value do
12
- map "source", to: :source
13
- map "destination", to: :destination
14
- map "type", to: :type
15
- end
16
10
  end
17
11
  end
@@ -21,46 +21,28 @@ module Edoxen
21
21
  # hard-coded path shapes) so adding new collection fields never requires
22
22
  # touching this class.
23
23
  class SchemaValidator
24
- class ValidationError < StandardError
25
- attr_reader :file, :line, :column, :pointer, :message_text
26
-
27
- def initialize(file:, line:, column:, pointer:, message_text:)
28
- @file = file
29
- @line = line
30
- @column = column
31
- @pointer = pointer
32
- @message_text = message_text
33
- super(format_line(file, line, column, message_text, pointer))
34
- end
35
-
36
- def to_clickable_format
37
- format_line(@file, @line, @column, @message_text, @pointer)
38
- end
39
-
40
- private
41
-
42
- def format_line(file, line, column, message_text, pointer)
43
- suffix = pointer.to_s.empty? ? "" : " at `#{pointer}`"
44
- "#{file}:#{line}:#{column}: #{message_text}#{suffix}"
45
- end
46
- end
24
+ # Back-compat alias. The canonical type is Edoxen::ValidationError;
25
+ # this constant lets existing callers keep writing
26
+ # `SchemaValidator::ValidationError` after the unification.
27
+ ValidationError = Edoxen::ValidationError
47
28
 
48
29
  def initialize(schema_path = default_schema_path)
49
30
  @schema_path = schema_path
50
31
  @schemer = load_schemer(schema_path)
51
32
  end
52
33
 
53
- # Validate a YAML file. Returns an array of ValidationError (empty = ok).
34
+ # Validate a YAML file. Returns an array of Edoxen::ValidationError
35
+ # (empty = ok).
54
36
  def validate_file(file_path)
55
37
  validate_content(File.read(file_path), file_path)
56
38
  rescue Errno::ENOENT
57
39
  [ValidationError.new(
58
40
  file: file_path, line: 1, column: 1,
59
- pointer: "", message_text: "File not found"
41
+ message_text: "File not found", source: Edoxen::ValidationError::SOURCE_SCHEMA
60
42
  )]
61
43
  end
62
44
 
63
- # Validate a YAML string. Returns an array of ValidationError.
45
+ # Validate a YAML string. Returns an array of Edoxen::ValidationError.
64
46
  def validate_content(content, file_path)
65
47
  data = normalize_dates(YAML.safe_load(content, permitted_classes: [Date, Time]))
66
48
  line_map = LineMap.build(content)
@@ -71,13 +53,15 @@ module Edoxen
71
53
  line, column = LineMap.locate(pointer, line_map)
72
54
  ValidationError.new(
73
55
  file: file_path, line: line, column: column,
74
- pointer: pointer, message_text: message
56
+ message_text: message, pointer: pointer,
57
+ source: Edoxen::ValidationError::SOURCE_SCHEMA
75
58
  )
76
59
  end
77
60
  rescue Psych::SyntaxError => e
78
61
  [ValidationError.new(
79
62
  file: file_path, line: e.line || 1, column: e.column || 1,
80
- pointer: "", message_text: "YAML syntax error: #{e.problem}"
63
+ message_text: "YAML syntax error: #{e.problem}",
64
+ source: Edoxen::ValidationError::SOURCE_SYNTAX
81
65
  )]
82
66
  end
83
67
 
@@ -8,11 +8,5 @@ module Edoxen
8
8
  attribute :ref, :string
9
9
  attribute :format, :string
10
10
  attribute :language_code, :string
11
-
12
- key_value do
13
- map "ref", to: :ref
14
- map "format", to: :format
15
- map "language_code", to: :language_code
16
- end
17
11
  end
18
12
  end
@@ -8,10 +8,5 @@ module Edoxen
8
8
  class StructuredIdentifier < Lutaml::Model::Serializable
9
9
  attribute :prefix, :string
10
10
  attribute :number, :string
11
-
12
- key_value do
13
- map "prefix", to: :prefix
14
- map "number", to: :number
15
- end
16
11
  end
17
12
  end
data/lib/edoxen/url.rb CHANGED
@@ -6,11 +6,5 @@ module Edoxen
6
6
  attribute :kind, :string, values: Enums::URL_KIND
7
7
  attribute :ref, :string
8
8
  attribute :format, :string
9
-
10
- key_value do
11
- map "kind", to: :kind
12
- map "ref", to: :ref
13
- map "format", to: :format
14
- end
15
9
  end
16
10
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Edoxen
4
- VERSION = "0.3.0"
4
+ VERSION = "0.3.1"
5
5
  end
data/lib/edoxen.rb CHANGED
@@ -21,6 +21,7 @@ module Edoxen
21
21
  # (Resolution <-> Localization, ResolutionMetadata <-> Localization, etc.).
22
22
  autoload :VERSION, "edoxen/version"
23
23
  autoload :Error, "edoxen/error"
24
+ autoload :ValidationError, "edoxen/error"
24
25
  autoload :Enums, "edoxen/enums"
25
26
 
26
27
  # Information-model classes (one per file, one concept per class).
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: edoxen
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.