1

I would like to create a custom scanner for i18n-tasks that can detect enums declared as hashes in models.

My enum declaration pattern will always be like this:

class Conversation < ActiveRecord::Base
  enum status: { active: 0, archived: 1}, _prefix: true
  enum subject: { science: 0, literature: 1, music: 2, art: 3 }, _prefix: true
end

The enums will always be declared as hashes, and will always have a numerical hash value, and will always have the option _prefix: true at the end of the declaration. There can be any number of values in the hash.

My custom scanner currently looks like this:

require 'i18n/tasks/scanners/file_scanner'
class ScanModelEnums < I18n::Tasks::Scanners::FileScanner
  include I18n::Tasks::Scanners::OccurrenceFromPosition

  # @return [Array<[absolute key, Results::Occurrence]>]
  def scan_file(path)
    text = read_file(path)
    text.scan(/enum\s([a-zA-Z]*?):\s\{.*\W(\w+):.*\}, _prefix: true$/).map do |prefix, attribute|
      occurrence = occurrence_from_position(
          path, text, Regexp.last_match.offset(0).first)
      model = File.basename(path, ".rb") #.split('/').last
      name = prefix + "_" + attribute
      ["activerecord.attributes.%s.%s" % [model, name], occurrence]
    end
  end
end

I18n::Tasks.add_scanner 'ScanModelEnums'

However this is only returning the very last element of each hash:

  • activerecord.attributes.conversation.status_archived
  • activerecord.attributes.conversation.subject_art

How can I return all the elements of each hash? I am wanting to see a result like this:

  • activerecord.attributes.conversation.status_active
  • activerecord.attributes.conversation.status_archived
  • activerecord.attributes.conversation.subject_science
  • activerecord.attributes.conversation.subject_literature
  • activerecord.attributes.conversation.subject_music
  • activerecord.attributes.conversation.subject_art

For reference, the i18n-tasks github repo offers an example of a custom scanner.

The file scanner class that it uses can be found here.

JohnRDOrazio
  • 1,358
  • 2
  • 15
  • 28
  • And instead of trying to output a single array, I also tried capturing everything between `{` and `}` and then splitting on `,` and mapping this array with `strip`, `split(":")` and one last `strip` so as to get all the enum attributes in an array; then looping over these and pushing what would've been the output array to another array, and finally outputting a 2d array. However I get error `key_occurrences.rb:48:in 'each': undefined method 'path'` in reference to the second enum attribute. – JohnRDOrazio Jul 16 '21 at 12:08

2 Answers2

2

This works:

def scan_file(path)
  result = []
  text = read_file(path)

  text.scan(/enum\s([a-zA-Z]*?):\s\{(.*)}, _prefix: true$/).each do |prefix, body|
    occurrence = occurrence_from_position(path, text, 
                                            Regexp.last_match.offset(0).first)

    body.scan(/(\w+):/).flatten.each do |attr|
      model = File.basename(path, ".rb")
      name = "#{prefix}_#{attr}" 
      result << ["activerecord.attributes.#{model}.#{name}", occurrence]
    end
  end
  result
end

It's similar to your 'answer' approach, but uses the regex to get all the contents between '{...}', and then uses another regex to grab each enum key name.

The probable reason your 'answer' version raises an error is that it is actually returning a three-dimensional array, not two:

  1. The outer .map is an array of all iterations.
  2. Each iteration returns retval, which is an array.
  3. Each element of retail is an array of ['key', occurrence] pairs.
rmlockerd
  • 3,776
  • 2
  • 15
  • 25
0

This isn't the answer, this is just the other attempt I made, which outputs a two dimensional array instead of a single array:

require 'i18n/tasks/scanners/file_scanner'
class ScanModelEnums < I18n::Tasks::Scanners::FileScanner
  include I18n::Tasks::Scanners::OccurrenceFromPosition

  # @return [Array<[absolute key, Results::Occurrence]>]
  def scan_file(path)
    text = read_file(path)
    text.scan(/enum\s([a-zA-Z]*?):\s\{(.*)\}, _prefix: true/).map do |prefix, attributes|
      retval = []
      model = File.basename(path, ".rb")
      names = attributes.split(",").map!{ |e| e.strip; e.split(":").first.strip }
      names.each do |attribute|
        pos = (Regexp.last_match.offset(0).first + 8 + prefix.length + attributes.index(attribute))
        occurrence = occurrence_from_position(
            path, text, pos)
        name = prefix + "_" + attribute
        # p "================"
        # p type
        # p message
        # p ["activerecord.attributes.%s.%s" % [model, name], occurrence]
        # p "================"
        retval.push(["activerecord.attributes.%s.%s" % [model, name], occurrence])
      end
      retval
    end
  end
end

I18n::Tasks.add_scanner 'ScanModelEnums'

This however gives me an error for the second detected attribute:

gems/i18n-tasks-0.9.34/lib/i18n/tasks/scanners/results/key_occurrences.rb:48:in `each': undefined method `path' for ["activerecord.attributes.conversation.status_archived", Occurrence(app/models/project.rb:3:32:98::)]:Array (NoMethodError)
JohnRDOrazio
  • 1,358
  • 2
  • 15
  • 28