Class: RichText::Op

Inherits:
Object
  • Object
show all
Defined in:
lib/rich-text/op.rb

Overview

Operations are the immutable units of rich-text deltas and documents. As such, we have a class that wraps these values and provides convenient methods for querying type and contents, and for subdividing as needed by Delta#slice.

Constant Summary collapse

TYPES =
[:insert, :retain, :delete].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(type, value, attributes = nil) ⇒ Op

Creates a new Op object, based on a type, value, and attributes. No sanity checking is performed on the arguments; please use parse for dealing with untrusted user input.

Parameters:

  • type (Symbol)

    one of TYPES

  • value (Integer, String, Hash)

    various values corresponding to type

  • attributes (Hash) (defaults to: nil)


51
52
53
54
55
# File 'lib/rich-text/op.rb', line 51

def initialize(type, value, attributes = nil)
  @type = type.to_sym
  @value = value.freeze
  @attributes = (attributes || {}).freeze
end

Instance Attribute Details

#attributesHash (readonly)

Returns:

  • (Hash)


13
14
15
# File 'lib/rich-text/op.rb', line 13

def attributes
  @attributes
end

#typeSymbol (readonly)

Returns one of TYPES.

Returns:

  • (Symbol)

    one of TYPES



9
10
11
# File 'lib/rich-text/op.rb', line 9

def type
  @type
end

#valueString, ... (readonly)

Returns value depends on type.

Returns:

  • (String, Integer, Hash)

    value depends on type



11
12
13
# File 'lib/rich-text/op.rb', line 11

def value
  @value
end

Class Method Details

.parse(data) ⇒ Op

Creates a new Op object from a Hash. Used by Delta#initialize to parse raw data into a convenient form.

Examples:

RichText::Op.parse({ insert: 'abc', attributes: { bold: true } })
# => #<RichText::Op insert="abc" {"bold"=>true}>

RichText::Op.parse({ insert: 'abc', retain: 3 })
# => ArgumentError: must be a Hash containing exactly one of the following keys: [:insert, :retain, :delete]

Parameters:

  • data (Hash)

    containing exactly one of TYPES as a key, and optionally an :attributes key. munged to provide indifferent access via String or Symbol keys

Returns:

Raises:

  • (ArgumentError)

    if data contains invalid keys, i.e. zero or more than one of TYPES



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/rich-text/op.rb', line 25

def self.parse(data)
  data = data.to_h.with_indifferent_access
  type_keys = (data.keys & TYPES.map(&:to_s))
  if type_keys.length != 1
    raise ArgumentError.new("must be a Hash containing exactly one of the following keys: #{TYPES.inspect}")
  end

  type = type_keys.first.to_sym
  value = data[type]
  if [:retain, :delete].include?(type) && !value.is_a?(Integer)
    raise ArgumentError.new("value must be an Integer when type is #{type.inspect}")
  end

  attributes = data[:attributes]
  if attributes && !attributes.is_a?(Hash)
    raise ArgumentError.new("attributes must be a Hash")
  end

  self.new(type, value, attributes)
end

Instance Method Details

#==(other) ⇒ Boolean Also known as: eql?

An Op is equal to another if type, value, and attributes all match

Parameters:

  • other (Op)

Returns:

  • (Boolean)


148
149
150
# File 'lib/rich-text/op.rb', line 148

def ==(other)
  other.is_a?(Op) && type == other.type && value == other.value && attributes == other.attributes
end

#attributes?Boolean

Returns whether any attributes are present; false when attributes is empty, true otherwise.

Examples:

RichText::Op.new(:insert, 'abc').attributes? # => false
RichText::Op.new(:insert, 'abc', {}).attributes? # => false
RichText::Op.new(:insert, 'abc', { bold: true }).attributes? # => true

Returns:

  • (Boolean)

    whether any attributes are present; false when attributes is empty, true otherwise.



62
63
64
# File 'lib/rich-text/op.rb', line 62

def attributes?
  !attributes.empty?
end

#delete?Boolean

Returns whether type is :delete or not

Returns:

  • (Boolean)


85
86
87
# File 'lib/rich-text/op.rb', line 85

def delete?
  type == :delete
end

#insert?(kind = Object) ⇒ Boolean

Returns whether type is :insert, and value is an instance of kind

Examples:

RichText::Op.new(:insert, 'abc').insert? # => true
RichText::Op.new(:insert, 'abc').insert?(String) # => true
RichText::Op.new(:insert, { image: 'http://i.imgur.com/y6Eo48A.gif' }).insert?(String) # => false

Parameters:

  • kind (Class) (defaults to: Object)

    pass a class to perform an additional is_a? check on #value

Returns:

  • (Boolean)


73
74
75
# File 'lib/rich-text/op.rb', line 73

def insert?(kind = Object)
  type == :insert && value.is_a?(kind)
end

#inspect(wrap = true) ⇒ String

A string useful for debugging, that includes type, value, and attributes.

Examples:

RichText::Op.new(:insert, 'abc', { bold: true }).inspect # => '#<RichText::Op insert="abc" {:bold=>true}>'
RichText::Op.new(:insert, 'abc', { bold: true }).inspect(false) => 'insert="abc" {:bold=>true}'

Parameters:

  • wrap (Boolean) (defaults to: true)

    pass false to avoid including the class name (used by Delta#inspect)

Returns:

  • (String)


139
140
141
142
143
# File 'lib/rich-text/op.rb', line 139

def inspect(wrap = true)
  str = "#{type}=#{value.inspect}"
  str << " #{attributes.inspect}" if attributes?
  wrap ? "#<#{self.class.name} #{str}>" : str
end

#lengthInteger

Returns a number indicating the length of this op, depending of the type:

  • for :insert, returns value.length if a String, 1 otherwise
  • for :retain and :delete, returns value

Returns:

  • (Integer)


94
95
96
97
98
99
100
101
# File 'lib/rich-text/op.rb', line 94

def length
  case type
  when :insert
    value.is_a?(String) ? value.length : 1
  when :retain, :delete
    value
  end
end

#retain?Boolean

Returns whether type is :retain or not

Returns:

  • (Boolean)


79
80
81
# File 'lib/rich-text/op.rb', line 79

def retain?
  type == :retain
end

#slice(start = 0, len = length) ⇒ Op

Returns a copy of the op with a subset of the value, measured in number of characters. An op may be subdivided if needed to return at most the requested length. Non-string inserts cannot be subdivided (naturally, as they have length 1).

Parameters:

  • start (Integer) (defaults to: 0)

    starting offset

  • len (Integer) (defaults to: length)

    how many characters

Returns:

  • (Op)

    whose length is at most len



108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/rich-text/op.rb', line 108

def slice(start = 0, len = length)
  if insert?(String)
    Op.new(:insert, value.slice(start, len), attributes)
  elsif insert?
    unless start == 0 && len == 1
      raise ArgumentError.new("cannot subdivide a non-string insert")
    end
    dup
  else
    Op.new(type, [value - start, len].min, attributes)
  end
end

#to_hHash

Returns the Hash representation of this object, the inverse of parse.

Returns:

  • (Hash)

    the Hash representation of this object, the inverse of parse



122
123
124
125
126
# File 'lib/rich-text/op.rb', line 122

def to_h
  { type => value }.tap do |json|
    json[:attributes] = attributes if attributes?
  end
end

#to_json(*args) ⇒ String

Returns the JSON representation of this object, by delegating to #to_h.

Returns:

  • (String)

    the JSON representation of this object, by delegating to #to_h



129
130
131
# File 'lib/rich-text/op.rb', line 129

def to_json(*args)
  to_h.to_json(*args)
end