Class: PlainText::Part

Inherits:
Object
  • Object
show all
Includes:
BuiltinType, Util
Defined in:
lib/plain_text/part.rb,
lib/plain_text/part/boundary.rb,
lib/plain_text/part/paragraph.rb,
lib/plain_text/part/string_type.rb

Overview

TODO:

methods

  • flatten

Class to represent a Chapter-like entity, which behaves like an Array

An instance of this class contains always an even number of elements, either another Part instance or Paragraph-type String-like instance, followed by a Boundary-type String-like instance. The first element is always a former and the last element is always a latter.

Essentially, the instance of this class holds the order information between sub-Part-s (< Array) and/or Paragraph-s (< String) and Boundary-s (< String).

An example instance looks like this:

Part (
  (0) Part::Paragraph::Empty,
  (1) Part::Boundary::General,
  (2) Part::ArticleHeader(
        (0) Part::Paragraph::Title,
        (1) Part::Boundary::Empty
      ),
  (3) Part::Boundary::TitleMain,
  (4) Part::ArticleMain(
        (0) Part::ArticleSection(
              (0) Part::Paragraph::Title,
              (1) Part::Boundary::General,
              (2) Part::Paragraph::General,
              (3) Part::Boundary::General,
              (4) Part::ArticleSubSection(...),
              (5) Part::Boundary::General,
              (6) Part::Paragraph::General,
              (7) Part::Boundary::Empty
            ),
        (1) Part::Boundary::General,
        (2) Part::Paragraph::General,
        (3) Part::Boundary::Empty
      ),
  (5) Part::Boundary::General
)

A Section (Part) always has an even number of elements: pairs of a Para (Part|Paragraph) and Boundary in this order.

This class behaves like an Array and so Array methods that usually return an Array returns an instance of this class, such as pt[0..-2]. However, not all Array methods are accepted in the same way. For example, for the instance pt, pt[0..-2] raises an Exception because it would violate the principle of the instance of this class having always an even number of elements. Use to_a to obtain a standard Array; e.g., pt.to_a[0..-2] returns an Array with an odd number of elements.

Some destructive Array operations, most notably #delete, #delete_if, #reject!, #select!, #filter!, #keep_if, #flatten!, #uniq! may alter the content in a way it breaks the self-consistency of the object. Use it at your own risk, if you wish (or don’t). Such operations may become prohibited in the future release. #<< and #delete_at are currently disabled.

An instance of this class is always non-equal to that of the standard Array class. To compare it at the Array level, convert a Part class instance into Array with #to_a first: pt.to_a == my_array

For CRUD of elements (contents) of an instance, the following methods are most basic:

  • Create:

    • Use PlainText::Part.new (see initialize)

  • Read:

    • All non-destructive Array operations are permitted and returns an instance of this class if the corresponding Array method returns an Array, providing it does not break the self-consistency.

    • #to_a gives or exposes the internal Array object, and then you can do whatever manipulation allowed for Array with it, if you insist. Note that destructive modification of the object carries a risk of breaking self-consistency of the instance and so is highly discouraged. You must know what you are doing if you do.

  • Update:

    • Insert/Append: #insert to insert. If the specified index is #size}, it means “append”. For primitive operations, specify primitive: true to skip various checks performed to guarantee the self-consistency as an instance of this class.

    • Replace: #[]= has some restrictions, such as, if multiple elements are replaced, they have to be pairs of Paragraph and Boundary. To skip all the checks, do #insert with primitive: true

  • Delete:

    • Delete: #slice! to delete. For primitive operations, specify primitive: true to skip various checks performed to guarantee the self-consistency as an instance of this class.

      • #delete_at is disabled. #delete, #delete_if, #reject!, #select!, #filter!, #keep_if (and #drop_while and #take_whie in recent Ruby) remain enabled, but if you use them, do so at your own risk, as no self-consistency checks would be performed automatically. #normalize would return the self-consistent instance, or its destructive version, #normalize!.

Author:

  • Masa Sakano (Wise Babel Ltd)

Defined Under Namespace

Modules: StringType Classes: Boundary, Paragraph

Constant Summary collapse

DISABLED_ARRAY_METHODS =

Array methods that are disabled for this class.

%i(<< delete_at)

Class Method Summary collapse

Instance Method Summary collapse

Methods included from BuiltinType

#subclass_name

Constructor Details

#initialize(arin, boundaries = nil, recursive: true, compact: true, compacter: true) ⇒ self

Constructor

New String-type objects are always created, i.e., regardless of whether the input is a pure String or Paragraph etc, a new Paragraph is always created from PlainText::Part::StringType#to_s. Therefore, even if one of the String given to the argument is destructively modified, it does not affect the generated PlainText::Part object. This also means that if the input Paragraph has special singloton methods or instance variables, the information will be lost.

Parameters:

  • arin (Array)

    of [Paragraph1, Boundary1, Para2, Bd2, …] or just of Paragraphs if boundaries is given as the second arguments

  • boundaries (Array) (defaults to: nil)

    of Boundary

  • recursive: (Hash) (defaults to: true)

    a customizable set of options

  • compact: (Hash) (defaults to: true)

    a customizable set of options

  • compacter: (Hash) (defaults to: true)

    a customizable set of options

Raises:

  • (ArgumentError)


119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/plain_text/part.rb', line 119

def initialize(arin, boundaries=nil, recursive: true, compact: true, compacter: true)
  raise ArgumentError, "Arguments must be an Array(s) or equivalent: "+arin.inspect+(boundaries ? ", "+boundaries.inspect : "") if (!arin.respond_to?(:compact) && !arin.respond_to?(:paras)) || boundaries && !boundaries.respond_to?(:compact)  # arin must be an Array or Part
  if !boundaries
    @array = arin.clone
    #super(arin)
  else
    raise ArgumentError, "Two main Arrays must have the same size." if arin.size != boundaries.size

    armain = []
    arin.each_with_index do |ea_e, i|
      armain << ea_e
      armain << (boundaries[i] || Boundary.new(''))
    end
    @array = armain
    #super armain
  end

  begin
    normalize!(recursive: recursive, compact: compact, compacter: compacter)
  rescue PlainText::PartNormalizeError => err
    raise TypeError, err.message
  end
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method_name, *args, **kwds) ⇒ Object

Basically delegates everything to Array

Array#<< and Array#delete_at are undefined because the instances of this class must take always an even number of elements.



671
672
673
674
675
676
677
678
# File 'lib/plain_text/part.rb', line 671

def method_missing(method_name, *args, **kwds)
  if DISABLED_ARRAY_METHODS.include? method_name
    raise NoMethodError, "no method "+method_name.to_s
  end
  _return_this_or_other{
    @array.public_send(method_name, *args, **kwds)
  }
end

Class Method Details

.boundary?(other) ⇒ Boolean

Returns true if it is Boundary

Returns:

  • (Boolean)


159
160
161
# File 'lib/plain_text/part.rb', line 159

def self.boundary?(other)
  _part_paragraph_boundary?(other, __method__)
end

.builtin_string?(other) ⇒ Boolean

Returns true if it is built-in String or similar

Returns:

  • (Boolean)


169
170
171
# File 'lib/plain_text/part.rb', line 169

def self.builtin_string?(other)
  other.respond_to?(:to_str) && !boundary? && !paragraph?
end

.paragraph?(other) ⇒ Boolean

Returns true if it is Paragraph

Returns:

  • (Boolean)


164
165
166
# File 'lib/plain_text/part.rb', line 164

def self.paragraph?(other)
  _part_paragraph_boundary?(other, __method__)
end

.parse(inprm, rule: PlainText::ParseRule::RuleConsecutiveLbs) ⇒ PlainText::Part

Parses a given string (or PlainText::Part) and returns this class of instance.

Parameters:

  • inprm (String, Array, Part)
  • rule: (Hash) (defaults to: PlainText::ParseRule::RuleConsecutiveLbs)

    a customizable set of options

Returns:



148
149
150
151
# File 'lib/plain_text/part.rb', line 148

def self.parse(inprm, rule: PlainText::ParseRule::RuleConsecutiveLbs)
  arin = rule.apply(inprm)
  self.new(arin)
end

.part?(other) ⇒ Boolean

Returns true if it is PlainText::Part

Returns:

  • (Boolean)


154
155
156
# File 'lib/plain_text/part.rb', line 154

def self.part?(other)
  _part_paragraph_boundary?(other, __method__)
end

Instance Method Details

#+(other) ⇒ Object

Plus operator

Parameters:

  • other (Object)

Returns:

  • as self

Raises:

  • (TypeError)


633
634
635
636
637
638
639
# File 'lib/plain_text/part.rb', line 633

def +(other)
  # # eg., if self is PlainText::Part::Section, the returned object is the same.
  # ret = self.class.new(self.paras+other_even_odd[0], self.boundaries+other_even_odd[1])
  raise(TypeError, "cannot operate with no #{self.class.name} instance (#{other.class.name})") if (!other.respond_to?(:to_ary) && !other.respond_to?(:normalize!))
  ret = self.class.new(@array+other.to_ary)
  ret.normalize!
end

#-(other) ⇒ Object

Minus operator

Parameters:

  • other (Object)

Returns:

  • as self

Raises:

  • (ArgumentError)


646
647
648
649
650
# File 'lib/plain_text/part.rb', line 646

def -(other)
  raise ArgumentError, "cannot operate with no {self.class.name} instance" if !other.respond_to?(:to_ary) || !other.respond_to?(:normalize!)
  ret = self.class.new(@array+other.to_ary)
  ret.normalize!
end

#==(other) ⇒ Object

Equal operator

Unless both are kind of Part instances, false is returned. If you want to make comparison in the Array level, do

p1.to_a == a1.to_a

Parameters:

  • other (Object)


620
621
622
623
624
625
626
# File 'lib/plain_text/part.rb', line 620

def ==(other)
  return false if  !other.respond_to?(:to_ary) || !other.respond_to?(:normalize!)
  %i(paras boundaries).each do |ea_m|  # %i(...) defined in Ruby 2.0 and later
    return false if !other.respond_to?(ea_m) || (self.public_send(ea_m) != other.public_send(ea_m))  # public_send() defined in Ruby 2.0 (1.9?) and later
  end
  @array == other.to_a  # or you may just return true?
end

#[](arg1, *rest) ⇒ Object Also known as: slice

Returns a partial Part-Array (or Object, if a single Integer is specified)

Because the returned object is this class of instance (when a pair of Integer or Range is specified), only an even number of elements, starting from an even number of index, is allowed.

Parameters:

  • arg1 (Integer, Range)
  • arg2 (Hash)

    a customizable set of options

Returns:

  • (Object)

Raises:

  • (RangeError)


694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
# File 'lib/plain_text/part.rb', line 694

def [](arg1, *rest)
  arg2 = rest[0]
  return @array[arg1] if !arg2 && !arg1.respond_to?(:exclude_end?)

  check_bracket_args_type_error(arg1, arg2)  # Args are now either (Int, Int) or (Range)

  if arg2
    size2ret = size2extract(arg1, arg2, ignore_error: true)  # maybe nil (if the index is too small).
    raise ArgumentError, ERR_MSGS[:even_num]+" "+ERR_MSGS[:use_to_a] if size2ret.odd?
    begin
      raise ArgumentError, "odd index is not allowed as the starting index for #{self.class.name}.  It must be even. "+ERR_MSGS[:use_to_a] if positive_array_index_checked(arg1, @array).odd?
    rescue TypeError, IndexError
      # handled by super
    end
    return _return_this_or_other{
      @array[arg1, arg2]
    }
  end

  begin
    rang = normalize_index_range(arg1)
  rescue IndexError #=> err
    return nil
    # raise RangeError, err.message
  end

  raise RangeError if rang.begin < 0 || rang.end < 0

  # The end is smaller than the begin in the positive index.  Empty instance of this class is returned.
  if rang.end < rang.begin
    return _return_this_or_other{
      @array[arg1, *rest]
    }
  end

  raise RangeError, "odd index is not allowed as the starting Range for #{sefl.class.name}.  It must be even. "+ERR_MSGS[:use_to_a] if rang.begin.odd?
  size2ret = size2extract(rang, skip_renormalize: true)
  raise ArgumentError, ERR_MSGS[:even_num]+" "+ERR_MSGS[:use_to_a] if size2ret.odd?
  _return_this_or_other{
    @array[arg1, *rest]
  }
end

#[]=(arg1, *rest) ⇒ Object

Replaces some of the Array content.

Parameters:

  • arg1 (Integer, Range)
  • arg2 (Hash)

    a customizable set of options

Returns:

  • (Object)

Raises:

  • (RangeError)


743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
# File 'lib/plain_text/part.rb', line 743

def []=(arg1, *rest)
  if rest.size == 1
    arg2, val = [nil, rest[-1]]
  else
    arg2, val = rest
  end

  # Simple substitution to a single element
  if !arg2 && !arg1.respond_to?(:exclude_end?)
    return _return_this_or_other{
      @array[arg1] = val
    }
  end

  check_bracket_args_type_error(arg1, arg2)  # Args are now either (Int, Int) or (Range)

  # raise TypeError, "object to replace must be Array type with an even number of elements." if !val.respond_to?(:to_ary) || val.size.odd?

  vals = (val.to_ary rescue [val])
  if arg2
    size2delete = size2extract(arg1, arg2, ignore_error: true)  # maybe nil (if the index is too small).
    raise ArgumentError, "odd-even parity of size of array to replace must be identical to that to slice." if size2delete && ((size2delete % 2) != (vals.size % 2))
    return _return_this_or_other{
      @array[arg1] = val
    }
  end

  begin
    rang = normalize_index_range(arg1)
  rescue IndexError => err
    raise RangeError, err.message
  end

  raise RangeError if rang.begin < 0 || rang.end < 0

  # The end is smaller than the begin in the positive index.  It is the same as insert (default in Ruby), except it returns the replaced Object (which may not be an Array).
  if rang.end < rang.begin
    insert(arg1, *vals)
    return val
  end

  size2delete = size2extract(rang, skip_renormalize: true)
  raise ArgumentError, "odd-even parity of size of array to replace must be identical to that to slice." if size2delete && ((size2delete % 2) != (vals.size % 2))
  @array[arg1] = val

  # The result may not be in an even number anymore.  Correct it.
  Boundary.insert(@array.size, "") if @array.size.odd?   ############## is it correct???

  # Original method may fill some elements of the array with String or even nil.  
  normalize!
end

#boundariesArray<Boundary>

Returns an array of boundaries (odd-number-index elements), consisting of Boundaries

Returns:

See Also:



204
205
206
# File 'lib/plain_text/part.rb', line 204

def boundaries
  @array.select.with_index { |_, i| i.odd? } rescue @array.select.each_with_index { |_, i| i.odd? } # Rescue for Ruby 2.1 or earlier
end

#boundary?Boolean

Unique instance methods (not existing in Array)

Returns:

  • (Boolean)


188
189
190
# File 'lib/plain_text/part.rb', line 188

def boundary?
  false
end

#boundary_extended_at(index) ⇒ Array?

returns all the Boundaries immediately before the index and at it as an Array

See #squash_boundary_at! to squash them.

Parameters:

  • index (Integer)

Returns:

  • (Array, nil)

    nil if a too large index is specified.



214
215
216
217
218
219
220
# File 'lib/plain_text/part.rb', line 214

def boundary_extended_at(index)
  (i_pos = get_valid_ipos_for_boundary(index)) || return
  arret = []
  prt = @array[i_pos-1]
  arret = prt.public_send(__method__, -1) if prt.respond_to? __method__
  arret << @array[index]
end

#clonePlainText::Part

Work around because Object#clone does not clone the instance variable @array

Returns:



609
610
611
# File 'lib/plain_text/part.rb', line 609

def clone
  dup_or_clone(super, __method__, '@array') # defined in builtin_type.rb
end

#compact!self, NilClass

Array#compact!

If changed, re-#normalize! it.

Returns:

  • (self, NilClass)


887
888
889
890
# File 'lib/plain_text/part.rb', line 887

def compact!
  ret = @array.send(__method__)
  ret ? normalize!(recursive: false) : ret
end

#concat(*rest) ⇒ self

Array#concat

Parameters:

  • rest (Array<Array>)

Returns:

  • (self)

See Also:



898
899
900
# File 'lib/plain_text/part.rb', line 898

def concat(*rest)
  insert(@array.size, *(rest.sum([])))
end

#deepcopyPart

Returns a dup-ped instance with all the Arrays and Strings dup-ped.

Returns:



225
226
227
228
229
230
# File 'lib/plain_text/part.rb', line 225

def deepcopy
  _return_this_or_other{
    @array.dup.map!{ |i| i.respond_to?(:deepcopy) ? i.deepcopy : (i.dup rescue i)}
    # the "rescue" deals with cases where i is immutable (which should never happen and in fact it would not raise an Exception in Ruby 3 seemingly).
  }
end

#dupPlainText::Part

Work around because Object#dup does not dup the instance variable @array

Returns:



602
603
604
# File 'lib/plain_text/part.rb', line 602

def dup
  dup_or_clone(super, __method__, '@array') # defined in builtin_type.rb
end

#each_boundary_with_index(**kwd, &bl) ⇒ Object

each method for boundaries only, providing also the index (always an odd number) to the block.

For just looping over the elements of #boundaries, do simply

boundaries.each do |ec|
end

The indices provided in this method are for the main Array, and hence different from #boundaries.each_with_index

Parameters:

  • recursive: (Hash)

    a customizable set of options

Returns:

  • as self



244
245
246
# File 'lib/plain_text/part.rb', line 244

def each_boundary_with_index(**kwd, &bl)
  map_boundary_core(do_map: false, with_index: true, **kwd, &bl)
end

#each_para_with_index(**kwd, &bl) ⇒ Object

each method for Paras only, providing also the index (always an even number) to the block.

For just looping over the elements of #paras, do simply

paras.each do |ec|
end

The indices provided in this method are for the main Array, and hence different from #paras.each_with_index

Parameters:

  • recursive: (Hash)

    a customizable set of options

Returns:

  • as self



260
261
262
# File 'lib/plain_text/part.rb', line 260

def each_para_with_index(**kwd, &bl)
  map_para_core(do_map: false, with_index: false, **kwd, &bl)
end

#first_significant_elementInteger?

The first significant (=non-empty) element.

If the returned value is non-nil and destructively altered, self changes.

Returns:

  • (Integer, nil)

    if self.empty? nil is returned.



269
270
271
272
# File 'lib/plain_text/part.rb', line 269

def first_significant_element
  (i = first_significant_index) || return
  @array[i]
end

#first_significant_indexInteger?

Index of the first significant (=non-empty) element.

If every element is empty, the last index is returned.

Returns:

  • (Integer, nil)

    if self.empty? nil is returned.



279
280
281
282
283
284
# File 'lib/plain_text/part.rb', line 279

def first_significant_index
  return nil if @array.empty?
  @array.find_index do |val|
    val && !val.empty?
  end || (@array.size-1)
end

#index_para?(i, accept_negative: true) ⇒ Boolean

True if the index should be semantically for Paragraph?

Parameters:

  • i (Integer)

    index for the array of self

  • accept_negative: (Hash) (defaults to: true)

    a customizable set of options

Returns:

  • (Boolean)

See Also:



291
292
293
# File 'lib/plain_text/part.rb', line 291

def index_para?(i, accept_negative: true)
  accept_negative ? positive_array_index_checked(i, @array).even? : i.even?
end

#insert(ind, *rest, primitive: false) ⇒ self

Array#insert

The most basic method to add/insert elements to self. Called from #[]= and #push, for example.

The maximum permitted position index is the size of self,s unlike Array#insert, which allows an index larger than the size, in which case nil is inserted in between.

The number of the inserted elements must be always even.

Parameters:

  • ind (Index)

    rest is inserted before ind if non-negative and after if negative.

  • rest (Array)

    This must have an even number of arguments.

  • primitive: (Hash) (defaults to: false)

    a customizable set of options

Returns:

  • (self)


809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
# File 'lib/plain_text/part.rb', line 809

def insert(ind, *rest, primitive: false)
  #return insert_original_b4_part(ind, *rest) if primitive
  return _return_this_or_other(@array.insert(ind, *rest)) if primitive

  ipos = positive_array_index_checked(ind, @array)  # IndexError may be raised.
  ipos += 1 if ind < 0
  # If ipos is negative, it should be inserted AFTER the index according to Array#index,
  # whereas if ipos is non-negative, inserted BEFORE.

  if ipos > @array.size
    raise IndexError, sprintf("index (%s) too large for array: maximum: %d.", ipos, @array.size)
  elsif rest.size.odd?
    raise ArgumentError, sprintf("number of arguments (%d) must be even.", rest.size)
  end

  return self if rest.empty?

  begin
    _return_this_or_other{
      @array.insert(ipos, *rest) && normalize! && self
    }
  rescue PlainText::PartNormalizeError => err
    raise TypeError, err.message
  end
end

#inspectString

Returns:



588
589
590
# File 'lib/plain_text/part.rb', line 588

def inspect
  self.class.name + @array.inspect
end

#last_significant_elementInteger?

The last significant (=non-empty) element.

If the returned value is non-nil and destructively altered, self changes.

Returns:

  • (Integer, nil)

    if self.empty? nil is returned.



300
301
302
303
# File 'lib/plain_text/part.rb', line 300

def last_significant_element
  (i = last_significant_index) || return
  @array[i]
end

#last_significant_indexInteger?

Index of the last significant (=non-empty) element.

If every element is empty, 0 is returned.

Returns:

  • (Integer, nil)

    if self.empty? nil is returned.



310
311
312
313
314
315
316
# File 'lib/plain_text/part.rb', line 310

def last_significant_index
  return nil if empty?
  (0..(size-1)).to_a.reverse.each do |i|
    return i if @array[i] && !@array[i].empty?  # self for sanity
  end
  return 0
end

#map_boundary(**kwd, &bl) ⇒ Object

map method for boundaries only, returning a copied self.

If recursive is true (Default), any Boundaries in the descendant Parts are also handled.

If a Boundary is set nil or empty, along with the preceding Paragraph, the pair is removed from the returned instance in Default (:compact and :compacter options

Parameters:

  • recursive: (Hash)

    a customizable set of options

Returns:

  • as self

See Also:



329
330
331
# File 'lib/plain_text/part.rb', line 329

def map_boundary(**kwd, &bl)
  map_boundary_core(with_index: false, **kwd, &bl)
end

#map_boundary_with_index(**kwd, &bl) ⇒ Object

map method for boundaries only, providing also the index (always an odd number) to the block, returning a copied self.

Parameters:

  • recursive: (Hash)

    a customizable set of options

Returns:

  • as self



337
338
339
# File 'lib/plain_text/part.rb', line 337

def map_boundary_with_index(**kwd, &bl)
  map_boundary_core(with_index: true, **kwd, &bl)
end

#map_para(**kwd, &bl) ⇒ Object

map method for Paras only, returning a copied self.

If recursive is true (Default), any Paras in the descendant Parts are also handled.

If a Paragraph is set nil or empty, along with the following Boundary, the pair is removed from the returned instance in Default (:compact and :compacter options

Parameters:

  • recursive: (Hash)

    a customizable set of options

Returns:

  • as self

See Also:



352
353
354
# File 'lib/plain_text/part.rb', line 352

def map_para(**kwd, &bl)
  map_para_core(with_index: false, **kwd, &bl)
end

#map_para_with_index(**kwd, &bl) ⇒ Object

map method for paras only, providing also the index (always an even number) to the block, returning a copied self.

Parameters:

  • recursive: (Hash)

    a customizable set of options

Returns:

  • as self



360
361
362
# File 'lib/plain_text/part.rb', line 360

def map_para_with_index(**kwd, &bl)
  map_para_core(with_index: false, **kwd, &bl)
end

#set(index1, index2, *rest) ⇒ self? #set(range) ⇒ self?

merge multiple paragraphs

The boundaries between them are simply joined as String as they are.

Overloads:

  • #set(index1, index2, *rest) ⇒ self?

    With a list of indices. Unless use_para_index is true, this means the main Array index. Namely, if Part is [P0, B0, P1, B1, P2, B2, B3] and if you want to merge P1 and P2, you specify as (2,3,4) or (2,4). If use_para_index is true, specify as (1,2).

    Parameters:

    • index1 (Integer)

      the first index to merge

    • index2 (Integer)

      the second index to merge, and so on…

  • #set(range) ⇒ self?

    With a range of the indices to merge. Unless use_para_index is true, this means the main Array index. See the first overload set about it.

    Parameters:

    • range (Range)

      describe value param

Parameters:

  • use_para_index (Boolean) (defaults to: false)

    If false (Default), the indices are for the main indices (alternative between Paras and Boundaries, starting from Para). If true, the indices are as obtained with #paras, namely the array containing only Paras.

Returns:

  • (self, nil)

    nil if nothing is merged (because of wrong indices).



410
411
412
413
414
415
416
# File 'lib/plain_text/part.rb', line 410

def merge_para!(*rest, use_para_index: false)
$myd = true
  (ranchk = build_index_range_for_merge_para!(*rest, use_para_index: use_para_index)) || (return self)  # Do nothing.
  # ranchk is guaranteed to have a size of 2 or greater.
  @array[ranchk] = [Paragraph.new(@array[ranchk][0..-2].join), @array[ranchk.end]]  # Array[Range] replaced with 2-elements (Para, Boundary)
  self
end

#merge_para_if {|ary, b1, b2, i| ... } ⇒ self, false

merge Paras if they satisfy the conditions.

A group of two Paras and the Boundaries in between and before and after is passed to the block consecutively.

Yields:

  • (ary, b1, b2, i)

    Returns true if the two paragraphs should be merged.

Yield Parameters:

  • ary (Array)

    of [Para1st, BoundaryBetween, Para2nd]

  • b1 (Boundary)

    Boundary-String before the first Para (nil for the first one)

  • b2 (Boundary)

    Boundary-String after the second Para

  • i (Integer)

    Index of the first Para

Yield Returns:

  • (Boolean, Symbol)

    True if they should be merged. :abort if cancel it.

Returns:

  • (self, false)

    false if no pairs of Paras are merged, else self.



376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
# File 'lib/plain_text/part.rb', line 376

def merge_para_if()
  arind2del = []  # Indices to delete (both paras and boundaries)
  @array.each_index do |ei|
    break if ei >= @array.size - 3  # 2nd last paragraph or later.
    next if !index_para?(ei, accept_negative: false)
    ar1st = @array[ei..ei+2]
    ar2nd = ((ei==0) ? nil : @array[ei-1])
    do_merge = yield(ar1st, ar2nd, @array[ei+3], ei)
    return false                 if do_merge == :abort
    arind2del.push ei, ei+1, ei+2 if do_merge 
  end

  return false if arind2del.empty? 
  arind2del.uniq!

  (arind2ranges arind2del).reverse.each do |er|
    merge_para!(er)
  end
  return self
end

#normalize(recursive: true, ignore_array_boundary: true, compact: true, compacter: true) ⇒ Object

Non-destructive version of #normalize!

Parameters:

  • recursive: (Hash) (defaults to: true)

    a customizable set of options

  • ignore_array_boundary: (Hash) (defaults to: true)

    a customizable set of options

  • compact: (Hash) (defaults to: true)

    a customizable set of options

  • compacter: (Hash) (defaults to: true)

    a customizable set of options

Returns:

  • as self

See Also:



501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
# File 'lib/plain_text/part.rb', line 501

def normalize(recursive: true, ignore_array_boundary: true, compact: true, compacter: true)
  # Trims pairs of consecutive Paragraph and Boundary of nil
  arall = @array
  size_parity = (@array.size.even? ? 0 : 1)
  if (@array.compact || compacter) && (@array.size > 0+size_parity)
    ((@array.size-2-size_parity)..0).each do |i| 
      # Loop over every Paragraph
      next if i.odd?
      arall.slice! i, 2 if compact   &&  !@array[i] && !@array[i+1]
      arall.slice! i, 2 if compacter && (!@array[i] || @array[i].empty?) && (!@array[i+1] || @array[i+1].empty?)
    end
  end

  i = -1
  self.class.new(
    arall.map{ |ea|
      i += 1
      normalize_core(ea, i, recursive: recursive)
    } + (arall.size.odd? ? [Boundary.new('')] : [])
  )
end

#normalize!(recursive: true, ignore_array_boundary: true, compact: true, compacter: true) ⇒ self

Normalize the content, making sure it has an even number of elements

The even and odd number elements are, if bare Strings or Array, converted into Paeagraph and Boundary, or Part, respectively. If not, Exception is raised. Note nil is conveted into either an empty Paragraph or Boundary.

Parameters:

  • recursive: (Hash) (defaults to: true)

    a customizable set of options

  • ignore_array_boundary: (Hash) (defaults to: true)

    a customizable set of options

  • compact: (Hash) (defaults to: true)

    a customizable set of options

  • compacter: (Hash) (defaults to: true)

    a customizable set of options

Returns:

  • (self)


472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
# File 'lib/plain_text/part.rb', line 472

def normalize!(recursive: true, ignore_array_boundary: true, compact: true, compacter: true)
  # Trim pairs of consecutive Paragraph and Boundary of nil
  size_parity = (@array.size.even? ? 0 : 1)
  if (@array.compact || compacter) && (@array.size > 0+size_parity)
    ((@array.size-2-size_parity)..0).each do |i| 
      # Loop over every Paragraph
      next if i.odd?
      @array.slice! i, 2 if @array.compact &&  !@array[i] && !@array[i+1]
      @array.slice! i, 2 if compacter      && (!@array[i] || @array[i].empty?) && (!@array[i+1] || @array[i+1].empty?)
    end
  end

  @array.map!.with_index{ |ea, ind|
    ax = normalize_core(ea, ind, recursive: recursive)
    ax
    #normalize_core(ea, ind, recursive: recursive)
  }
  @array.insert(@array.size, Boundary.new('')) if @array.size.odd?
  self
end

#paragraph?Boolean

Returns:

  • (Boolean)


192
193
194
# File 'lib/plain_text/part.rb', line 192

def paragraph?
  false
end

#parasArray<Part, Paragraph>

Returns an array of Paras (even-number-index elements), consisting of Part and/or Paragraph

Returns:

See Also:



528
529
530
531
# File 'lib/plain_text/part.rb', line 528

def paras
  @array.select.with_index { |_, i| i.even? } rescue @array.select.each_with_index { |_, i| i.even? } # Rescue for Ruby 2.1 or earlier
  # ret.freeze
end

#part?Boolean

Returns:

  • (Boolean)


196
197
198
# File 'lib/plain_text/part.rb', line 196

def part?
  true
end

#push(*rest) ⇒ self Also known as: append

Array#push

Parameters:

  • rest (Array)

Returns:

  • (self)

See Also:



908
909
910
# File 'lib/plain_text/part.rb', line 908

def push(*rest)
  concat(rest)
end

#reparse(**kwd) ⇒ PlainText::Part

Non-destructive version of #reparse!

Parameters:

  • rule (Hash)

    a customizable set of options

  • name (Hash)

    a customizable set of options

  • range (Hash)

    a customizable set of options

Returns:



548
549
550
551
552
# File 'lib/plain_text/part.rb', line 548

def reparse(**kwd)
  ret = @array.dup
  ret.reparse!(**kwd)
  ret
end

#reparse!(rule: PlainText::ParseRule::RuleConsecutiveLbs, name: nil, range: (0..-1)) ⇒ self

Reparses self or a part of it.

Parameters:

  • rule (Hash) (defaults to: PlainText::ParseRule::RuleConsecutiveLbs)

    a customizable set of options

  • name (Hash) (defaults to: nil)

    a customizable set of options

  • range (Hash) (defaults to: (0..-1))

    a customizable set of options

Options Hash (rule:):

Options Hash (name:):

  • Identifier (String, Symbol, Integer, nil)

    of rule, if need to specify.

Options Hash (range:):

  • Range (Range, nil)

    of indices of self to reparse. In Default, the entire self.

Returns:

  • (self)


539
540
541
542
# File 'lib/plain_text/part.rb', line 539

def reparse!(rule: PlainText::ParseRule::RuleConsecutiveLbs, name: nil, range: (0..-1))
  insert range.begin, self.class.parse((range ? @array[range] : self), rule: rule, name: name)
  self
end

#respond_to_missing?(method_name, *rest) ⇒ Boolean

Redefines the behaviour of respond_to? (essential when defining method_missing)

Returns:

  • (Boolean)


681
682
683
# File 'lib/plain_text/part.rb', line 681

def respond_to_missing?(method_name, *rest)  # include_all=false
  !DISABLED_ARRAY_METHODS.include?(method_name) && @array.respond_to?(method_name, *rest) || super
end

#slice!(arg1, *rest, primitive: false) ⇒ Object

Delete elements and return the deleted content or nil if nothing is deleted.

The number of elements to be deleted must be even.

Parameters:

  • arg1 (Integer, Range)
  • arg2 (Hash)

    a customizable set of options

  • primitive: (Hash) (defaults to: false)

    a customizable set of options

Returns:

  • as self or NilClass

Raises:

  • (ArgumentError)


844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
# File 'lib/plain_text/part.rb', line 844

def slice!(arg1, *rest, primitive: false)
  #return slice_original_b4_part!(arg1, *rest) if primitive
  return _return_this_or_other(@array.slice(ind, *rest)) if primitive

  arg2 = rest[0]

  # Simple substitution to a single element
  raise ArgumentError, ERR_MSGS[:even_num] if !arg2 && !arg1.respond_to?(:exclude_end?)

  check_bracket_args_type_error(arg1, arg2)  # Args are now either (Int, Int) or (Range)

  if arg2
    size2delete = size2extract(arg1, arg2, ignore_error: true)  # maybe nil (if the index is too small).
    raise ArgumentError, ERR_MSGS[:even_num] if size2delete && size2delete.odd?
    raise ArgumentError, "odd index is not allowed as the starting Range for #{self.class.name}.  It must be even." if arg1.odd?  # Because the returned value is this class of instance.
    return _return_this_or_other(@array.slice!(arg1, *rest))
  end

  begin
    rang = normalize_index_range(arg1)
  rescue IndexError => err
    raise RangeError, err.message
  end

  raise RangeError if rang.begin < 0 || rang.end < 0

  return _return_this_or_other(@array.slice!(arg1, *rest)) if (rang.begin > rang.end)  # nil or [] is returned
  

  size2delete = size2extract(rang, skip_renormalize: true)
  raise ArgumentError, ERR_MSGS[:even_num] if size2delete && size2delete.odd? 
  raise ArgumentError, "odd index is not allowed as the starting Range for #{self.class.name}.  It must be even." if rang.begin.odd?  # Because the returned value is this class of instance.
  _return_this_or_other(@array.slice!(arg1, *rest))
end

#squash_boundaries!self

Wrapper of #squash_boundary_at! to loop over the whole PlainText::Part

Returns:

  • (self)


573
574
575
576
577
578
# File 'lib/plain_text/part.rb', line 573

def squash_boundaries!
  each_boundary_with_index do |ec, i|
    squash_boundary_at!(i)
  end
  self
end

#squash_boundary_at!(index) ⇒ Boundary?

Emptifies all the Boundaries immediately before the Boundary at the index and squashes it to the one at it.

See #boundary_extended_at to view them.

Parameters:

  • index (Integer)

Returns:

  • (Boundary, nil)

    nil if a too large index is specified.



561
562
563
564
565
566
567
# File 'lib/plain_text/part.rb', line 561

def squash_boundary_at!(index)
  (i_pos = get_valid_ipos_for_boundary(index)) || return
  prt = @array[i_pos-1]
  m = :emptify_last_boundary!
  @array[i_pos] << prt.public_send(m) if prt.respond_to? m
  @array[i_pos]
end

#to_aArray Also known as: to_ary, instance

Returns:

  • (Array)


593
594
595
# File 'lib/plain_text/part.rb', line 593

def to_a
  @array
end