Module: Vehicles::Plates

Defined in:
lib/vehicles/plates.rb,
lib/vehicles/plates/series.rb,
lib/vehicles/plates/jurisdiction.rb

Overview

License-plate & registration-mark knowledge (the VehiclesDB PRD-PLATES dataset, bundled offline like everything else in this gem). Ask it two kinds of question:

Vehicles.plates                 # => every Jurisdiction
Vehicles.plates(:nl)            # => one Jurisdiction (series, charset, authority)
Vehicles.plate("12-GB-BD", jurisdiction: :nl)   # => Plates::Match

Matching is TWO-TIER, because humans type plates every way at once:

exact   — the input, upcased, matches a series' STRICT regex as-typed
        (separators included): formatted exactly as issued.
lenient — the input with separators stripped ("1234XYZ", "1234 xyz",
        "1234-XYZ" all collapse to "1234XYZ") matches the same strict
        alphabet with the separators removed. Still the authority's
        real issuing alphabet — only the punctuation is forgiven.

A lenient-only match carries suggestion: the serial re-formatted the way it appears on the physical plate ("did you mean 1234 XYZ"). Same posture as every lookup in this gem: garbage in => empty result, never an exception.

Defined Under Namespace

Classes: Jurisdiction, Match, Series

Constant Summary collapse

DATA_DIR =
File.expand_path("../../data/plates", __dir__)
SEPARATORS =

What people type between plate groups: space(s), hyphen/en/em dash, middot, period. Stripped for the lenient tier (and from the strict regexes to derive their lenient twins).

/[\s\-–—·.]+/

Class Method Summary collapse

Class Method Details

.classesObject



47
48
49
# File 'lib/vehicles/plates.rb', line 47

def classes
  @classes ||= YAML.safe_load_file(File.join(DATA_DIR, "_meta", "classes.yml")) || {}
end

.jurisdiction(code) ⇒ Object



43
44
45
# File 'lib/vehicles/plates.rb', line 43

def jurisdiction(code)
  jurisdictions.find { |j| j.code == code.to_s.downcase.tr("_", "-") }
end

.jurisdictionsObject



37
38
39
40
41
# File 'lib/vehicles/plates.rb', line 37

def jurisdictions
  @jurisdictions ||= Dir.glob(File.join(DATA_DIR, "*.yml")).sort.map do |path|
    Jurisdiction.load(path)
  end
end

.match(input, jurisdiction:) ⇒ Object

The matcher behind Vehicles.plate. Unknown jurisdiction => empty Match.



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/vehicles/plates.rb', line 52

def match(input, jurisdiction:)
  juris = jurisdiction(jurisdiction)
  typed = input.to_s.upcase.strip
  canon = typed.gsub(SEPARATORS, "")
  return Match.new(input: input.to_s, jurisdiction: juris, canon: canon, hits: []) if juris.nil? || canon.empty?

  hits = juris.series.filter_map do |series|
    next nil unless series.validation_regexp

    exact = series.validation_regexp.match?(typed)
    lenient = exact || series.lenient_regexp&.match?(canon)
    Match::Hit.new(series: series, exact: exact) if lenient
  end

  Match.new(input: input.to_s, jurisdiction: juris, canon: canon, hits: hits)
end