Class: CSV

Inherits:
Object
  • Object
show all
Defined in:
lib/clir/CSV_extension.rb

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.headers_for_testObject (readonly)

Pour pouvoir tester l’entête dans les tests



6
7
8
# File 'lib/clir/CSV_extension.rb', line 6

def headers_for_test
  @headers_for_test
end

Class Method Details

.readlines_backward(path, **options, &block) ⇒ Object Also known as: readlines_backwards, foreach_backward, foreach_backwards

Lecture d’un fichier CSV à partir de la fin

Les arguments sont les mêmes que pour readlines

Pour rappel :

+options+ peut contenir
  :headers    À true, on tient compte des entêtes et on
              retourne des CSV::Row. Sinon, on retourne une
              Array simple.
  :headers_converters 
              Convertisseur pour les noms des colonnes.
              :downcase ou :symbol
  :converters
              Liste de convertisseurs pour les données.
              Principalement :numeric, :date
  :col_sep
              Séparateur de colonne. Par défaut une virgule.


26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/clir/CSV_extension.rb', line 26

def readlines_backward(path, **options, &block)

  options ||= {}
  options.key?(:col_sep) || options.merge!(col_sep: ',')

  file = File.new(path)
  size = File.size(file)

  if size < ( 1 << 16 )
    return readlines_backward_in_small_file(path, **options, &block)
  end

  #
  # Si options[:headers] est true, il faut récupérer la première
  # ligne et la transformer en entête
  # @note Cet entête permettra d'instancier les rangées (\CSV::Row)
  # 
  headers = nil
  if options[:headers]
    begin
      fileh   = File.open(path,'r')
      header  = fileh.gets.chomp
    ensure
      fileh.close
    end
    table = CSV.parse(header, **options)
    headers = table.headers
    @headers_for_test = headers # pour les tests
  end


  if block_given?
    #
    #
    # Avec un bloc fourni, on va lire ligne par ligne en partant
    # de la fin.
    # 
    buffer_size = 10000 # disons que c'est la longueur maximale d'une ligne

    #
    # On se positionne de la fin - la longueur du tampo dans le
    # fichier à lire
    # 
    file.seek(-buffer_size, IO::SEEK_END)
    # 
    # Le tampon courant (il contient tout jusqu'à ce qu'il y ait
    # au moins une ligne)
    # 
    buffer = ""
    #
    # On boucle tant qu'on n'interrompt pas (pour le moment)
    # 
    while true
      #
      # On lit la longueur du tampon en l'ajoutant à ce qu'on a 
      # déjà lu ou ce qui reste.
      # Celui pourra contenir une ou plusieurs lignes, la première
      # pourra être tronquée
      # 
      buffer = file.read(buffer_size) + buffer
      #
      # Nombre de lignes
      # (utile ?)
      nombre_lignes = buffer.count("\n")
      # 
      # On traite les lignes du buffer (en gardant ce qui dépasse)
      # 
      if nombre_lignes > 0
        # puts "Position dans le fichier : #{file.pos}".bleu
        # puts "Nombre de lignes : #{nombre_lignes}".bleu
        lines = buffer.split("\n").reverse
        #
        # On laisse la dernière ligne, certainement incomplète, dans
        # le tampon. Elle sera ajoutée à la fin de ce qui précède
        # 
        buffer = lines.pop
        # 
        # Boucle sur les lignes
        # 
        # @note : un break, dans le &block, interrompra la boucle
        # 
        lines.each do |line|
          line = line.chomp

          # Je crois que c'est ça qui prend trop de temps
          # line = "#{header}\n#{line}\n" if options[:headers]
          # puts "line parsée : #{line.inspect}".bleu
          # line_csv = CSV.parse(line, **line_csv_options) 
          # puts "line_csv: #{line_csv.inspect}::#{line_csv.class}".orange
          # yield line_csv[0]
          # 

          # 
          # Convertir les valeurs si des convertisseurs sont
          # définis
          # NOTE : Pour le moment, je reste simple et un peu brut
          # 
          values = line.split(options[:col_sep])
          if options[:converters]
            values = values.collect do |value|
              options[:converters].each do |converter|
                if converter == :numeric && value.numeric?
                  value = value.to_i and break
                elsif converter == :date && value.match?(/[0-9]{2,4}/)
                  begin
                    value = Date.parse(value)
                    break
                  rescue nil
                  end
                end
              end
              value
            end
          end

          row = CSV::Row.new(headers, values)

          yield row
        end
      end
      #
      # On remonte de deux fois la longueur du tampon. Une fois pour
      # revenir au point de départ, une fois pour remonter à la
      # portion suivante, à partir de la position courante, évide-
      # ment
      # 
      new_pos = file.pos - 2 * buffer_size
      if new_pos < 0
        file.seek(new_pos)
      else
        file.seek(- 2 * buffer_size, IO::SEEK_CUR)
      end
      #
      # Si on se trouve à 0, on doit s'arrêter
      # 
      break if file.pos <= 0
      # puts "Nouvelle position dans le fichier : #{file.pos}".bleu
    end
  else
    #
    # Sans bloc fourni, on renvoie tout le code du fichier
    # 
    # À vos risques et périls
    # self.readlines(path, **options).to_a.reverse
    self.foreach(path, **options).reverse
  end

end

.readlines_backward_in_small_file(path, **options, &block) ⇒ Object

Lecture à l’envers dans un petit fichier



180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/clir/CSV_extension.rb', line 180

def readlines_backward_in_small_file(path, **options, &block)
  if block_given?
    self.foreach(path, **options).reverse_each do |row|
      yield row
    end
    # liste2reverse = []
    # self.readlines(path, **options).each { |row| liste2reverse << row }
    # liste2reverse.reverse.each do |row|
    #   yield row
    # end
  else
    # Lecture toute simple de la table
    # return self.readlines(path, **options).to_a.reverse
    return self.foreach(path, **options).reverse
  end
end