<commit_before>module EPUB
  class CFI < Struct.new(:path, :range)
    include Comparable

    SPECIAL_CHARS = '^[](),;=' # "5E", "5B", "5D", "28", "29", "2C", "3B", "3D"
    RE_ESCAPED_SPECIAL_CHARS = Regexp.escape(SPECIAL_CHARS)

    class << self
      def escape(string)
        string.gsub(/([#{RE_ESCAPED_SPECIAL_CHARS}])/o, '^\1')
      end

      def unescape(string)
        string.gsub(/\^([#{RE_ESCAPED_SPECIAL_CHARS}])/o, '\1')
      end
    end

    # @todo consider range
    def <=>(other)
      path <=> other.path
    end

    class Path < Struct.new(:step, :local_path)
      include Comparable

      def <=>(other)
        cmp = step <=> other.step
        return cmp unless cmp == 0
        local_path <=> other.local_path
      end
    end

    class Range < Struct.new(:start, :end)
    end

    class LocalPath < Struct.new(:steps, :redirected_path, :offset)
      include Comparable

      def <=>(other)
        cmp = steps <=> other.steps
        return cmp unless cmp == 0
        cmp = redirected_path <=> other.redirected_path
        return cmp unless cmp == 0
        return -1 if offset.nil? and !other.offset.nil?
        return 1 if !offset.nil? and other.offset.nil?
        offset <=> other.offset
      end
    end

    class RedirectedPath < Struct.new(:path, :offset)
      include Comparable

      def <=>(other)
        cmp = path <=> other.path
        return cmp unless cmp == 0
        offset <=> other.offset
      end
    end

    class Step < Struct.new(:step, :assertion)
      include Comparable

      def <=>(other)
        step <=> other.step
      end
    end

    class IDAssertion < Struct.new(:id, :parameters)
      def to_s
        s = "[#{CFI.escape(id)}"
        parameters.each_pair do |key, value|
          s << ";#{CFI.escape(key)}=#{CFI.escape(value)}"
        end
        s << ']'
      end
    end

    class TextLocationAssertion < Struct.new(:preceded, :followed, :parameters)<commit_msg>Make IDAssertion not be instance of Struct<commit_after>module EPUB
  class CFI < Struct.new(:path, :range)
    include Comparable

    SPECIAL_CHARS = '^[](),;=' # "5E", "5B", "5D", "28", "29", "2C", "3B", "3D"
    RE_ESCAPED_SPECIAL_CHARS = Regexp.escape(SPECIAL_CHARS)

    class << self
      def escape(string)
        string.gsub(/([#{RE_ESCAPED_SPECIAL_CHARS}])/o, '^\1')
      end

      def unescape(string)
        string.gsub(/\^([#{RE_ESCAPED_SPECIAL_CHARS}])/o, '\1')
      end
    end

    # @todo consider range
    def <=>(other)
      path <=> other.path
    end

    class Path < Struct.new(:step, :local_path)
      include Comparable

      def <=>(other)
        cmp = step <=> other.step
        return cmp unless cmp == 0
        local_path <=> other.local_path
      end
    end

    class Range < Struct.new(:start, :end)
    end

    class LocalPath < Struct.new(:steps, :redirected_path, :offset)
      include Comparable

      def <=>(other)
        cmp = steps <=> other.steps
        return cmp unless cmp == 0
        cmp = redirected_path <=> other.redirected_path
        return cmp unless cmp == 0
        return -1 if offset.nil? and !other.offset.nil?
        return 1 if !offset.nil? and other.offset.nil?
        offset <=> other.offset
      end
    end

    class RedirectedPath < Struct.new(:path, :offset)
      include Comparable

      def <=>(other)
        cmp = path <=> other.path
        return cmp unless cmp == 0
        offset <=> other.offset
      end
    end

    class Step < Struct.new(:step, :assertion)
      include Comparable

      def <=>(other)
        step <=> other.step
      end
    end

    class IDAssertion
      attr_accessor :id, :parameters

      def initialize(id, parameters={})
        @id, @parameters = id, parameters
      end

      def to_s
        s = "[#{CFI.escape(id)}"
        parameters.each_pair do |key, value|
          s << ";#{CFI.escape(key)}=#{CFI.escape(value)}"
        end
        s << ']'
      end
    end

    class TextLocationAssertion < Struct.new(:preceded, :followed, :parameters)
{'fraction_non_alphanumeric': 0.134173461823573, 'fraction_numerical': 0.008895478131949592, 'mean_word_length': 2.164972634870993, 'pattern_counts': {'":': 0, '<': 58, '<?xml version=': 0, '>': 31, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '58060', 'n_tokens_mistral': 1378, 'n_tokens_neox': 1324, 'n_words': 336}