Module: ZipCode::FR

Defined in:
lib/zipcode-fr.rb

Overview

TODO: factor index system out TODO: factor country-independent code out rubocop:disable Metrics/ModuleLength

Class Method Summary collapse

Class Method Details

.append_infixes(idx, pos, val, min_size: 1) ⇒ Object



141
142
143
144
145
146
147
# File 'lib/zipcode-fr.rb', line 141

def append_infixes(idx, pos, val, min_size: 1)
  each_prefix(val, min_size: min_size) do |prefix|
    each_suffix(prefix, min_size: min_size) do |infix|
      idx[infix.hash] << pos
    end
  end
end

.append_match(idx, pos, val) ⇒ Object



104
105
106
# File 'lib/zipcode-fr.rb', line 104

def append_match(idx, pos, val)
  idx[val.hash] << pos
end

.append_prefixes(idx, pos, val, min_size: 1) ⇒ Object



121
122
123
# File 'lib/zipcode-fr.rb', line 121

def append_prefixes(idx, pos, val, min_size: 1)
  each_prefix(val, min_size: min_size) { |prefix| idx[prefix.hash] << pos }
end

.append_word_prefixes(idx, pos, val) ⇒ Object



114
115
116
117
118
# File 'lib/zipcode-fr.rb', line 114

def append_word_prefixes(idx, pos, val)
  each_word(val) do |word|
    each_prefix(word) { |prefix| idx[prefix.hash] << pos }
  end
end

.append_words(idx, pos, val) ⇒ Object



109
110
111
# File 'lib/zipcode-fr.rb', line 109

def append_words(idx, pos, val)
  each_word(val) { |w| idx[w.hash] << pos }
end

.appender(idx, key, mode) ⇒ Object

TODO: create an appender registry rubocop:disable Metrics/AbcSize rubocop:disable Metrics/MethodLength



88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/zipcode-fr.rb', line 88

def appender(idx, key, mode)
  case mode
  when :prefix
    -> (pos, record) { append_prefixes(idx, pos, record[key]) }
  when :infix
    -> (pos, record) { append_infixes(idx, pos, record[key]) }
  when :word
    -> (pos, record) { append_words(idx, pos, record[key]) }
  when :word_prefix
    -> (pos, record) { append_word_prefixes(idx, pos, record[key]) }
  else
    -> (pos, record) { append_match(idx, pos, record[key]) }
  end
end

.clean(row) ⇒ Object



55
56
57
# File 'lib/zipcode-fr.rb', line 55

def clean(row)
  row_to_h(row_clean(row))
end

.complete(name, str, key = nil) ⇒ Object



182
183
184
185
# File 'lib/zipcode-fr.rb', line 182

def complete(name, str, key = nil)
  key ||= name
  search(name, str).map { |e| e[key] }
end

.data_sourceObject



23
24
25
26
# File 'lib/zipcode-fr.rb', line 23

def data_source
  path = 'vendor/data/code_postaux_v201410.csv'
  File.expand_path(File.join(File.dirname(__FILE__), '..', path))
end

.each_prefix(val, min_size: 1) ⇒ Object



131
132
133
# File 'lib/zipcode-fr.rb', line 131

def each_prefix(val, min_size: 1)
  min_size.upto(val.length) { |i| yield val[0...i] }
end

.each_suffix(val, min_size: 1) ⇒ Object



136
137
138
# File 'lib/zipcode-fr.rb', line 136

def each_suffix(val, min_size: 1)
  min_size.upto(val.length) { |i| yield val[-i..-1] }
end

.each_word(val, &block) ⇒ Object



126
127
128
# File 'lib/zipcode-fr.rb', line 126

def each_word(val, &block)
  val.split.each(&block)
end

.index(name) ⇒ Object



150
151
152
153
154
155
156
# File 'lib/zipcode-fr.rb', line 150

def index(name)
  if @indexes.key?(name)
    @indexes[name]
  else
    fail "no index named #{name.inspect}"
  end
end

.index!(name, data, modes = nil, key: nil) ⇒ Object



70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/zipcode-fr.rb', line 70

def index!(name, data, modes = nil, key: nil)
  key ||= name
  index = Hash.new { |h, k| h[k] = [] unless h.frozen? }

  modes = [modes] unless modes.is_a?(Enumerable)
  modes.each do |mode|
    data.each(&appender(index, key, mode))
  end

  index.each { |_, v| v.uniq! }
  index.freeze

  @indexes[name] = index
end

.loadObject



12
13
14
15
16
17
# File 'lib/zipcode-fr.rb', line 12

def load
  # TODO: non-optimal, but not overly long either
  index!(:name, reader, [:word_prefix, :match])
  index!(:zip, reader, :prefix)
  @loaded = true
end

.memsize_of_index(name) ⇒ Object



159
160
161
162
163
# File 'lib/zipcode-fr.rb', line 159

def memsize_of_index(name)
  require 'objspace'
  ObjectSpace.memsize_of(@indexes[name]) +
    @indexes[name].reduce(0) { |a, (_, v)| a + ObjectSpace.memsize_of(v) }
end

.openObject



37
38
39
40
41
42
# File 'lib/zipcode-fr.rb', line 37

def open
  CSV.open(data_source, 'rb', reader_options) do |csv|
    csv.take(1)  # skip header manually to preserve tell()
    yield csv
  end
end

.read_at(*positions, count: 1) ⇒ Object



165
166
167
168
169
170
171
172
173
174
# File 'lib/zipcode-fr.rb', line 165

def read_at(*positions, count: 1)
  return enum_for(:read_at, *positions, count: count) unless block_given?

  open do |io|
    positions.each do |pos|
      io.seek(pos)
      io.take(count).each { |row| yield clean(row) }
    end
  end
end

.readerObject



45
46
47
48
49
50
51
52
# File 'lib/zipcode-fr.rb', line 45

def reader
  return enum_for(:reader) unless block_given?

  open do |io|
    pos = io.tell
    io.each { |row| yield(pos, clean(row)); pos = io.tell }
  end
end

.reader_optionsObject



29
30
31
32
33
34
# File 'lib/zipcode-fr.rb', line 29

def reader_options
  {
    col_sep: ';',
    encoding: 'ISO-8859-1',
  }
end

.ready?Boolean

Returns:

  • (Boolean)


19
20
21
# File 'lib/zipcode-fr.rb', line 19

def ready?
  @loaded
end

.row_clean(row) ⇒ Object



60
61
62
# File 'lib/zipcode-fr.rb', line 60

def row_clean(row)
  row.map { |e| e.strip.encode('UTF-8') }
end

.row_to_h(row) ⇒ Object



65
66
67
# File 'lib/zipcode-fr.rb', line 65

def row_to_h(row)
  [:insee, :name, :zip, :alt_name].zip(row).to_h
end

.search(name, str, case_insensitive: true) ⇒ Object



177
178
179
180
# File 'lib/zipcode-fr.rb', line 177

def search(name, str, case_insensitive: true)
  str = str.upcase if case_insensitive
  read_at(*index(name)[str.hash])
end