Module: Opencdd::Parcel::LayoutDetector

Defined in:
lib/opencdd/parcel/layout_detector.rb

Overview

Single source of truth for Parcel on-disk layout detection.

Three layouts exist in the wild:

[legacy single] one export_*.xls file per type (CLASS, PROPERTY, RELATION, UNIT, VALUELIST, VALUETERMS). [flat dir] a directory containing those six files at the top level. [sharded dir] a directory containing one subdir per class code, each subdir holding 1-6 export_*.xls files either directly (flat sharded) or under a UNID-named version subfolder (per-version sharded, identified by _entity.json).

Previously Reader, FlatDirReader, ShardedDirReader, and ScrapeVerifier each held their own copies of the regexes and detection helpers. Layout drift meant patching N files. Now they all delegate here.

Constant Summary collapse

CLASS_CODE_PATTERN =

Class-code shape per IEC CDD conventions: 2-6 uppercase letters, 1-6 digits, optional alnum suffix.

/\A[A-Z]{2,6}[0-9]{1,6}[A-Z0-9-]*\z/.freeze
UNID_PATTERN =

Harvester per-version subfolder name (Lotus Notes UNID).

/\A[0-9A-F]{32}\z/i.freeze
FILE_PATTERN =

Per-type export filename produced by cdd.iec.ch's download endpoint.

/\Aexport_(CLASS|PROPERTY|RELATION|UNIT|VALUELIST|VALUETERMS)_[^\.]+\.(xls|xlsx)\z/i.freeze

Class Method Summary collapse

Class Method Details

.active_xls_dir(code_dir) ⇒ Object

Returns the directory holding the active export_*.xls files for a given class-code dir, or nil when no XLS is present.

Resolution order:

1. Per-version: +_entity.json+ names a UNID subfolder.
2. Per-version (no manifest): a single UNID subfolder with XLS.
3. Flat sharded: XLS files directly under +code_dir+.


64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/opencdd/parcel/layout_detector.rb', line 64

def active_xls_dir(code_dir)
  return nil unless File.directory?(code_dir)

  # Per-version with manifest.
  manifest = File.join(code_dir, "_entity.json")
  if File.file?(manifest)
    current = read_current_version_dir(manifest)
    if current && unid?(current)
      candidate = File.join(code_dir, current)
      return candidate if File.directory?(candidate)
    end
  end

  # Per-version without manifest: unique UNID subfolder with XLS.
  unid_subdirs = Dir.children(code_dir)
                    .select { |n| unid?(n) }
                    .map { |n| File.join(code_dir, n) }
                    .select { |p| File.directory?(p) }
  if unid_subdirs.size == 1
    active = unid_subdirs.first
    return active if Dir.children(active).any? { |f| legacy_export?(f) }
  end

  # Flat sharded.
  return code_dir if Dir.children(code_dir).any? { |f| legacy_export?(f) }

  nil
end

.class_code?(name) ⇒ Boolean

True if name looks like a class code (AAA001, UAC696, ...).

Returns:

  • (Boolean)


40
41
42
43
# File 'lib/opencdd/parcel/layout_detector.rb', line 40

def class_code?(name)
  return false if name.nil?
  name.match?(CLASS_CODE_PATTERN)
end

.legacy_export?(filename) ⇒ Boolean

True if filename is one of the harvester's export_* files.

Returns:

  • (Boolean)


46
47
48
49
# File 'lib/opencdd/parcel/layout_detector.rb', line 46

def legacy_export?(filename)
  return false if filename.nil?
  filename.match?(FILE_PATTERN)
end

.sharded_class_subdir?(path) ⇒ Boolean

True if path is a sharded class-code subdir (contains XLS directly or via the per-version layout).

Returns:

  • (Boolean)


95
96
97
98
99
# File 'lib/opencdd/parcel/layout_detector.rb', line 95

def sharded_class_subdir?(path)
  return false unless File.directory?(path)
  return false unless class_code?(File.basename(path))
  !active_xls_dir(path).nil?
end

.unid?(name) ⇒ Boolean

True if name is a UNID-named subfolder.

Returns:

  • (Boolean)


52
53
54
55
# File 'lib/opencdd/parcel/layout_detector.rb', line 52

def unid?(name)
  return false if name.nil?
  name.match?(UNID_PATTERN)
end