Class: Jig

Inherits:
Object
  • Object
show all
Defined in:
lib/jig.rb,
lib/jig/css.rb,
lib/jig/xml.rb

Overview

A jig is an ordered sequence of objects and named gaps. During construction, a gap is represented by a symbol:

Jig.new('left', :middle, 'right')   # => <#Jig: ["left", :middle, "right"]>
Jig.new(1, :two, 3.0, "4")          # => <#Jig: [1, :two, 3.0, "4"]>
Jig.new(lambda { rand(6) })         # => <#Jig: [#<Proc:0x00437ee8>]

As a convenience, a block provided to Jig::new is added to the sequence as a proc:

Jig.new { rand(6) }                 # => #<Jig: [#<Proc:0x00026660@-:2>]>

Jig#[] also constructs new jig instances:

Jig[:header, :body, :footer]        # [:header, :body, :footer]

At any time, a jig can be converted to a string by Jig#to_s. The string is constructed by concatenating string representations of the objects in the jig as follows:

  • strings are concatenated as-is

  • gaps are skipped

  • procs are evaluated, the results converted to a string, and concatenated

  • other objects are converted by calling to_s and the resulting string is concatenated

A variety of operations are provided to manipulate jigs but the most common is the ‘plug’ operation:

comment = Jig.new("/*", :comment, "*/")
partial = comment.plug(:comment, "This is a ", :adjective, " comment")
puts partial.plug(:adjective, 'silly')        # => /* This is a silly comment */
puts partial.plug(:adjective, 'boring')       # => /* This is a boring comment */

The plug method constructs a new jig that shares all the contents of the previous jig but with the named gap replaced by one or more objects. If there are more than one gaps with the same name, they are all replaced with the same sequence of objects.

j = Jig.new("first", :separator, "middle", :separator, "after")
puts j.plug(:separator, '/')                  # => "first/middle/last"

Direct Known Subclasses

CSS, XML

Defined Under Namespace

Classes: AGap, ALGap, CSS, Gap, XML

Constant Summary collapse

VERSION =
'0.1.2'
DEFAULT_GAP =

:section: Construction

Gap.new
GAP =
DEFAULT_GAP.name
Before =

Convert a string into a jig. The string is scanned for blocks deliminated by %…. The blocks are interpreted as follows:

%{:identifier:}          is converted into a gap named *identifier*
%{!code!}                is converted to a lambda

Code blocks are interpreted when the resulting jig is rendered via Jig#to_s. Each time parse is called, an anonymous module is created to evaluate all the code blocks created during that call to parse. Alternatively, the code blocks can be evaluated against an explicit binding passed as the second argument.

Jig.parse("abc").to_s     # abc
Jig.parse("1 %{:x} 3")    # Jig[1, :x, 3]
Jig.parse("1 %{:x} 3")    # Jig[1, :x, 3]

a = 5
Jig.parse("%{a + 1}", binding).to_s    #  6
Jig.parse("%{b + 1}").to_s             #  NameError

class A
  def to_jig
    Jig.parse("secret: %{secret}", binding)
  end
  def secret
     "xyzzy"
  end
  private :secret
end

A.new.secret          # NoMethodError
A.new.to_jig.to_s     # secret: xyzzy
/[^%]*/
Replace =
/%\{(.?)(.+)\1\}/

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*items, &block) ⇒ Jig

Construct a jig from the list of items. Symbols in the list are replaced with a gap named by the symbol.

j1 = Jig.new('first', :middle, 'last')      # => #<Jig: ['first', :middle, 'last']
j1.gaps                                     # => [:middle]

If a block is provided, it is appended as a proc to the list of items. Procs within a jig are not evaluated until the jig is rendered as a string by to_s.

i = 0
j = Jig.new("i is ") { i }
puts j                        # => "i is 0"
i = 1
puts j                        # => "i is 1"

If no arguments are given and no block is given, the jig is constructed with a single default gap named :___ (also known as Jig::GAP).

one_gap = Jig.new
one_gap.gaps           # => [:___]


264
265
266
267
268
269
270
# File 'lib/jig.rb', line 264

def initialize(*items, &block)
  @contents = [[]]
  @rawgaps = []
  items.push(block) if block
  items.push(DEFAULT_GAP) if items.empty?
  concat(items)
end

Instance Attribute Details

#contentsObject

the sequence of objects



152
153
154
# File 'lib/jig.rb', line 152

def contents
  @contents
end

#rawgapsObject

the unplugged gaps



155
156
157
# File 'lib/jig.rb', line 155

def rawgaps
  @rawgaps
end

Class Method Details

.interpolate(replace, context) ⇒ Object



225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/jig.rb', line 225

def interpolate(replace, context)
  all, delimiter, content = *(replace.match(Replace))

  case delimiter
  when ':'
    content.to_sym
  when '!', ''
    eval("lambda {#{content}}", context)
  else
    parse_other(delimiter, content)
  end
end

.nullObject

Construct a null jig. A null jig has no contents and no gaps. It can be considered analogous to an empty array or a null string.

jigs = (1..3).map { |i| Jig[i, :___, i] }
aggregate = jigs.inject(Jig.null) { |s,j| s + j }
puts aggregate.plug('x')    # 1x12x23x3


166
167
168
# File 'lib/jig.rb', line 166

def null
  new(nil)
end

.parse(string = nil, context = nil) ⇒ Object



204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/jig.rb', line 204

def parse(string=nil, context=nil)
  wrapper = context || Module.new.class_eval { binding }
  scanner = StringScanner.new(string)
  raw = []
  while !scanner.eos?
    if before = scanner.scan(Before)
      if replace = scanner.scan(Replace)
        raw << before
        raw << interpolate(replace, wrapper)
      else
        raw << before
        raw << scanner.getch unless scanner.eos?
      end
    else
      raw << scanner.rest
      scanner.terminate
    end
  end
  Jig.new(*raw)
end

.parse_other(delim, stripped) ⇒ Object

Raises:

  • (ArgumentError)


238
239
240
# File 'lib/jig.rb', line 238

def parse_other(delim, stripped)
  raise ArgumentError, "invalid delimiter: \"#{delim}\""
end

Instance Method Details

#+(obj) ⇒ Object Also known as: append

call-seq:

jig + obj -> a_jig

Duplicate the current jig then use concat to add obj.

j = Jig[1, :alpha]
j + 2                     # Jig[1, :alpha, 2]
j + :beta                 # Jig[1, :alpha, :beta]
j + Jig[:beta]            # Jig[1, :alpha, :beta]
j + [3,4]                 # Jig[1, :alpha, 3, 4]
j + [Jig.new, Jig.new]    # Jig[1, :alpha, :___, :___]
j + Jig[:beta] * 2        # Jig[1, :alpha, :beta, :beta]


933
934
935
# File 'lib/jig.rb', line 933

def +(obj)
  dup.concat(obj)
end

#==(rhs) ⇒ Object

Returns true if rhs is an instance of Jig or one of Jig’s subclasses and the two jigs have equal gap lists and contents (via Array#==). Procs are not evaluated by Jig#==.



323
324
325
326
# File 'lib/jig.rb', line 323

def ==(rhs)
  Jig === rhs &&
  contents.zip(rawgaps).flatten == rhs.contents.zip(rhs.rawgaps).flatten
end

#===(rhs) ⇒ Object

Returns true if the string representation of the jig matches the rhs.to_str using String#=~. Procs are evaluated by Jig#===.



344
345
346
# File 'lib/jig.rb', line 344

def ===(rhs)
  to_s =~ rhs.to_str
end

#=~(rhs) ⇒ Object

Returns true if the string representation of the jig matches the rhs using String#=~. Procs are evaluated by Jig#=~.

Jig.new("chitchat") =~ Jig.new("chit", "chat")        # => true
Jig.new("chitchat") =~ Jig.new("chit", :gap, "chat")  # => true
Jig.new("chitchat") =~ /chit/                         # => true
Jig.new(1,:a,2) =~ Jig.new(1, 2, :a)                  # => true


334
335
336
337
338
339
340
# File 'lib/jig.rb', line 334

def =~(rhs)
  if Regexp === rhs
    to_s =~ rhs
  else
    to_s == rhs.to_s
  end
end

#after(*args) ⇒ Object

call-seq:

after(symbol, item, ...)      -> a_jig
after(item, ...)              -> a_jig

A new jig is constructed by inserting the items after the specified gap or the default gap if the first argument is not a symbol. The gap itself remains in the new jig.

Jig.new.after(1,2,3)           # => #<Jig: [:___, 1, 2, 3]>
Jig.new.after(:a, 1,2,3)       # => #<Jig: [:___]>
Jig.new(:a, :b).after(:a, 1)   # => #<Jig: [:a, 1, :b]>


632
633
634
# File 'lib/jig.rb', line 632

def after(*args)
  dup.after!(*args)
end

#after!(*args) ⇒ Object

call-seq:

after!(symbol, item, ...)      -> a_jig
after!(item, ...)              -> a_jig

Like #after but modifies the current jig.



806
807
808
809
810
811
812
813
814
815
816
817
# File 'lib/jig.rb', line 806

def after!(*args)
  if Symbol === args.first
    gap = args.shift
  else
    gap = GAP
  end
  if current = rawgaps.find {|x| x.name == gap}
    plug!(gap, args.unshift(current))
  else
    self
  end
end

#before(*args) ⇒ Object

call-seq:

before(symbol, item, ...)      -> a_jig
before(item, ...)              -> a_jig

Returns a new jig constructed by inserting the item before the specified gap or the default gap if the first argument is not a symbol. The gap itself remains in the new jig.

Jig.new.before(1,2,3)           # => #<Jig: [1, 2, 3, :___]>
Jig.new.before(:a, 1,2,3)       # => #<Jig: [:___]>
Jig.new(:a, :b).before(:b, 1)   # => #<Jig: [:a, 1, :b]>


618
619
620
# File 'lib/jig.rb', line 618

def before(*args)
  dup.before!(*args)
end

#before!(*args) ⇒ Object

call-seq:

before!(symbol, item, ...)      -> a_jig
before!(item, ...)              -> a_jig

Like #before but modifies the current jig.



788
789
790
791
792
793
794
795
796
797
798
799
# File 'lib/jig.rb', line 788

def before!(*args)
  if Symbol === args.first
    gap = args.shift
  else
    gap = GAP
  end
  if current = rawgaps.find {|x| x.name == gap}
    plug!(gap, args.push(current))
  else
    self
  end
end

#closed?Boolean

Returns true if the jig has no gaps.

Jig.new.closed?            # false
Jig.new('a').closed?       # true
Jig.new.plug('a').closed?  # true

Returns:

  • (Boolean)


366
367
368
# File 'lib/jig.rb', line 366

def closed?
  rawgaps.empty?
end

#concat(collection) ⇒ Object

The collection is converted to a list of items via *collection. Resulting items are pushed onto the end of the current jig.

j = Jig.new 1
j.concat([2,3])
j == Jig[1,2,3]                # true

j.concat Jig[4,:alpha,5]
j == Jig[1,2,3,4,:alpha,5]     # true


729
730
731
# File 'lib/jig.rb', line 729

def concat(collection)
  push(*collection)
end

#eql?(rhs) ⇒ Boolean

Returns true if the two jigs are instances of the same class and have equal gap lists and contents (via Array#eql?). Jigs that are not equal may still have the same string representation. Procs are not evaluated by Jig#eql?.

Returns:

  • (Boolean)


309
310
311
312
# File 'lib/jig.rb', line 309

def eql?(rhs)
  self.class == rhs.class &&
  contents.zip(rawgaps).flatten.eql?(rhs.contents.zip(rhs.rawgaps).flatten)
end

#fill!Object

Calls the block once for each gap in the jig passing the name of the gap. If the block returns the gapname, the gap remains in the jig, otherwise the gap is replaced with the return value of the block. If called without a block, all the gaps are replaced with the empty string.



834
835
836
837
838
839
840
841
842
843
844
845
# File 'lib/jig.rb', line 834

def fill!
  adjust = 0
  gaps.each_with_index do |gap, index|
    match = index + adjust
    items = block_given? && yield(gap)
    if items != gap
      fill = rawgaps.at(match).fill(items)
      adjust += plug_gap!(match, fill) - 1
    end
  end
  self
end

#filln!(*indices) ⇒ Object

Calls the block once for each index passing the index to the block. The gap is replaced with the return value of the block. If called without a block, the indexed gaps are replaced with the empty string.



851
852
853
854
855
856
857
858
859
860
861
862
863
# File 'lib/jig.rb', line 851

def filln!(*indices)
  # XXX need to handle indices that are too small
  adjust = 0
  normalized = indices.map { |x| (x >= 0) && x || (x+rawgaps.size) }.sort
  normalized.each do |index|
    match = index + adjust
    gap = rawgaps.fetch(match)
    items = block_given? && yield(index)
    fill = gap.fill(items)
    adjust += plug_gap!(match, fill) - 1
  end
  self
end

#freezeObject

Applies Kernel#freeze to the jig and its internal structures. A frozen jig may still be used with non-mutating methods such as #plug but an exception will be raised if a mutating method such as #push or #plug! are called.



659
660
661
662
663
664
# File 'lib/jig.rb', line 659

def freeze
  super
  @contents.freeze
  @rawgaps.freeze
  self
end

#gapsObject

Returns an array containing the names, in order, of the gaps in the current jig. A name may occur more than once in the list.

Jig.new.gaps                # => [:___]
Jig.new(:a, :b).gaps        # => [:a, :b]
Jig.new(:a, :b).plug.gaps   # => []


392
393
394
# File 'lib/jig.rb', line 392

def gaps
  rawgaps.map { |g| g.name }
end

#has_gap?(name) ⇒ Boolean

Returns true if the named gap appears in the jig.

Jig.new.has_gap? :___       # => true
Jig.new.plug.has_gap? :___  # => false

Returns:

  • (Boolean)


399
400
401
# File 'lib/jig.rb', line 399

def has_gap?(name)
  rawgaps.find {|g| g.name == name }
end

#index(name) ⇒ Object

Returns the position of the first gap with the given name or nil if a gap is not found. See slice for a description of the indexing scheme for jigs.

Jig.new.index(:___)         # => 1
Jig.new.index(:a)           # => nil
Jig.new(:a,:b).index(:b)    # => 3


409
410
411
412
# File 'lib/jig.rb', line 409

def index(name)
  rawgaps.each_with_index {|g,i| return (i*2)+1 if g.name == name }
  nil
end

#initialize_copy(other) ⇒ Object

The internal structure of a jig is duplicated on #dup or #clone, but not the objects that make up the contents of the jig. This is analogous to how an array is duplicated.



275
276
277
278
279
# File 'lib/jig.rb', line 275

def initialize_copy(other)
  super
  @contents = other.contents.dup
  @rawgaps = other.rawgaps.dup
end

#inspectObject

The inspect string for a jig is an array of objects with gaps represented by symbols. Gaps with associated filters are shown with trailing braces (:gap{}).

Jig.new.inspect                                     # => #<Jig: [:___]>
Jig.new(1,:a,2).inspect                             # => #<Jig: [1, :a, 2]>
Jig.new(Gap.new(:example) { |x| x.to_s.reverse })  # => #<Jig: [:example{}]>


357
358
359
360
# File 'lib/jig.rb', line 357

def inspect
  info = rawgaps.map {|g| g.terse_inspect }
  "#<Jig: #{contents.zip(info).flatten[0..-2].inspect}>"
end

#join(sep = $,) ⇒ Object

The contents of the jig are joined via Array#join.



652
653
654
# File 'lib/jig.rb', line 652

def join(sep=$,)
  contents.join(sep)
end

#mult(rhs) ⇒ Object Also known as: *

call-seq:

jig * count  -> a_jig
jig * array  -> a_jig

With an integer argument, a new jig is constructed by concatenating count copies of self.

three = Jig.new * 3      # => Jig[:___, :___, :___]
puts three.plug('3')     # => "333"

With an array argument, the elements of the array are used to plug the default gap. The resulting jigs are concatenated to form the final result:

require 'yaml'
item = Jig["- ", :___, "\n"]    # => #<Jig: ["- ", :___, "\n"]>
list = item * [1,2,3]           # => #<Jig: ["- ", 1, "\n", "- ", 2, "\n", "- ", 3, "\n"]>
puts list                       # => "- 1\n- 2\n- 3\n"
puts YAML.load(list.to_s)       # => [1, 2, 3]


512
513
514
515
516
517
518
519
520
521
522
# File 'lib/jig.rb', line 512

def mult(rhs)
  case rhs
  when Integer
    raise ArgumentError, "count must be greater than zero" if rhs < 1
    (1...rhs).inject(dup)  { |j,i| j.push(self) }
  when Array
    rhs.inject(Jig.null) { |j,x| j.concat( plug(x) ) }
  else
    raise ArgumentError, "rhs operand for * must be Integer or Array, was #{rhs.class})"
  end
end

#null?Boolean

Returns true if the jig has no gaps and renders as the empty string. This method will cause proc objects within the jig to be evaluated.

Jig.new.null?           # false
Jig.new(nil).null?      # true
Jig.new.plug("").null?  # true

Returns:

  • (Boolean)


383
384
385
# File 'lib/jig.rb', line 383

def null?
  closed? && to_s.empty?
end

#open?Boolean

Returns true if the jig has any gaps.

Jig.new.open?            # true
Jig.new('a').open?       # false
Jig.new.plug('a').open?  # false

Returns:

  • (Boolean)


374
375
376
# File 'lib/jig.rb', line 374

def open?
  not rawgaps.empty?
end

#plug(*args, &block) ⇒ Object Also known as: %

call-seq:

plug                      -> a_jig
plug { |gap| ... }        -> a_jig
plug(hash)                -> a_jig
plug(symbol, item, ...)   -> a_jig
plug(item, ...)           -> a_jig

Duplicates the current jig, plugs one or more named gaps, and returns the result. Plug silently ignores attempts to fill undefined gaps. In all cases, the replacement items are inserted into the jig as during Jig construction (see Jig#new).

If called with no arguments, any remaining gaps are plugged with nil.

If called with a block, the name of each gap in the jig is passed to the block and the gap is replaced with the return value of the block.

If called with a hash, the keys are used as gap names and the values are used to plug the respective gaps. The gaps are effectively plugged in parallel to avoid any ambiguity when gaps are plugged with jigs that themselves contain additional gaps.

If called with a single symbol argument, the default gap is replaced with a new gap named by the symbol.

If two or more arguments are provided and the first argument is a symbol, the named gap is replaced with the list of items.

In all other cases, the default gap is replaced with the list of items.

j = Jig.new                 # => #<Jig: [:___]>
jg = Jig[:gamma, :epsilon]  # => #<Jig: [:gamma, :epsilon]>

j.plug :alpha               # => #<Jig: [:alpha]>
j.plug 1                    # => #<Jig: [1]>
j.plug :alpha, 'a'          # => #<Jig: ['a']>
jg.plug :gamma, ['a', 'b']  # => #<Jig: ['a', 'b', :epsilon]>
jg.plug :gamma => 'a', 
        :epsilon => 'e'     # => #<Jig: ['a', 'e']>
j.plug [1,2,3]              # => #<Jig: [1, 2, 3]>
j.plug                      # => #<Jig: []>


568
569
570
# File 'lib/jig.rb', line 568

def plug(*args, &block)
  dup.plug!(*args, &block)
end

#plug!(*args, &block) ⇒ Object Also known as: []=, <<

call-seq:

plug!                      -> a_jig
plug! { |gap| ... }        -> a_jig
plug!(hash)                -> a_jig
plug!(symbol, *items)      -> a_jig
plug!(*items)              -> a_jig

Plugs one or more named gaps (see #plug) and returns self. The current jig is modified. To construct a new jig use #plug instead. If the named plug is not defined, the jig is not changed.



743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
# File 'lib/jig.rb', line 743

def plug!(*args, &block)
  return fill!(&block) if block or args.empty?
  first, *more = args
  case first
  when Hash
    fill! { |g| first.fetch(g, g) }
  when Symbol 
    if more.empty?
      fill! { |g| g == GAP ? first : g }
    else
      fill! { |g| g == first ? (x = *more) : g }
    end
  else
    fill! { |g| g == GAP ? (x = *args) : g }
  end
end

#plug_gap!(gap, plug) ⇒ Object

:stopdoc: This method alters the current jig by replacing a gap with a (possibly empty) sequence of objects. The contents and rawgap arrays are modified such that the named gap is removed and the sequence of objects are put in the logical position of the former gap.

Gaps and contents are maintainted in two separate arrays. Each element in the contents array is a list of objects implemented as an array. The first element of the gap array represents the gap between the the first and second element of the contents array.

   +----+----+
   |    |    |     <--- rawgaps array
   +----+----+
+----+----+----+
|    |    |    |   <--- contents array
+----+----+----+

The following relation always holds: rawgaps.size == contents.size - 1 :startdoc: Replaces the named gap in the current jig with plug and returns the number of gaps that were inserted in its place.



887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
# File 'lib/jig.rb', line 887

def plug_gap!(gap, plug)
  case plug
  when String
    contents[gap,2] = [contents[gap] + [plug] + contents[gap+1]]
    rawgaps.delete_at(gap)
    return 0
  when nil, []
    contents[gap,2] = [contents[gap] + contents[gap+1]]
    rawgaps.delete_at(gap)
    return 0
  else
    plug = Jig[*plug] unless Jig === plug
    filling, gaps = plug.contents, plug.rawgaps
  end

  case filling.size
  when 0
    contents[gap,2] = [contents[gap] + contents[gap+1]]
  when 1
    contents[gap,2] = [contents[gap] + filling.first + contents[gap+1]]
  else
    contents[gap,2] = [contents[gap] + filling.first] + filling[1..-2] + [filling.last + contents[gap+1]]
  end
  rawgaps[gap, 1] = gaps
  gaps.size
end

#plugn(*args, &block) ⇒ Object

call-seq:

plugn(n, item)       -> a_jig
plugn(range, array)  -> a_jig
plugn(symbol, array) -> a_jig
plugn(array)         -> a_jig
plugn(hash)          -> a_jig
plugn(item)          -> a_jig

Similar to #plug but gaps are identified by an integer offset, not by name. Unlike #index, and #slice, #plugn assumes that gaps are indexed consecutively starting with 0.

  • When the first argument is an integer, n, the n-th gap is replaced with the item.

  • When the first argument is a range, the gaps indexed by range are replaced with the items in array.

  • When the only argument is an array, the gaps indexed by 0...array.size are replaced with the items in the array.

  • When the only argument is a hash, the keys of the hash are taken as indices and the respective gaps are replaced with the associated values from the hash.

  • Any other single argument is taken as the replacement for the first gap.

Examples:

list = Jig[:item, ',', :item, ',', :item]
list.plugn(1, 'second')                           # => ",second,"
list.plugn(1..2, %w{second third})                # => ",second,third"
list.plugn('first')                               # => "first,,"
list.plugn(%w{first second})                      # => "first,second,"
list.plugn(0 => 'first', 2 => 'third')            # => "first,,third"


604
605
606
# File 'lib/jig.rb', line 604

def plugn(*args, &block)
  dup.plugn!(*args, &block)
end

#plugn!(first = nil, second = nil, &block) ⇒ Object

Same as #plug but modifies self.



763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
# File 'lib/jig.rb', line 763

def plugn!(first=nil, second=nil, &block)

  return filln!(&block) if block or !first

  case first
  when Hash
    filln!(*first.keys) { |i| first[i] }
  when Integer 
    #filln!(first => second)
    filln!(first) { second }
  when Array
    filln!(*(0...first.size)) { |index| first.fetch(index) }
  when Range 
    # pairs = first.inject({}) { |p, i| p[i] = second[i-first.begin]; p }
    filln!(*first) { |index| second && second.fetch(index-first.begin) }
  else
    filln!(0) { first }
  end
end

#push(*items) ⇒ Object

Pushes the items onto the end of the current jig. The current jig is modified. Use jig.dup.push(*items) if you want a fresh jig. Individual items are handled as follows:

  • strings: pushed as is

  • symbols: converted to a gap and pushed

  • gaps: pushed as is

  • jigs: each item of the other jig is pushed in order to the current jig, including gaps.

  • any object that responds to to_jig is converted and the results pushed.

  • any object that responds to call is pushed as a proc.

  • all other objects are pushed as is.



679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
# File 'lib/jig.rb', line 679

def push(*items)
  items.each do |i|
    case i
    when String   then 
      contents.last << i
    when Symbol   then 
      rawgaps << Gap.new(i)
      contents << []
    when Jig      then 
      push_jig i
    when NilClass, FalseClass then 
      next
    when Jig::Gap then 
      rawgaps << i
      contents << []
    else 
      if respond_to?(p = "push_#{i.class.name.downcase}")
        send(p, i)
      elsif i.respond_to? :to_jig
        push_jig i.to_jig
      elsif i.respond_to? :call
        (class <<i; self; end).class_eval {
          undef inspect
          #:stopdoc:
          alias inspect :to_s
          undef to_s
          def to_s; call.to_s; end
          def to_yaml(opts={}); call.to_yaml(opts); end
          #:startdoc:
        }
        contents.last << i
      else
        contents.last << i
      end
    end
    #contents.last.concat(add)
  end
  self
end

#push_jig(other) ⇒ Object

Append a jig onto the end of the current jig.



916
917
918
919
920
# File 'lib/jig.rb', line 916

def push_jig(other)
  self.contents = contents[0..-2] + [contents[-1] + other.contents[0]] + other.contents[1..-1]
  rawgaps.concat other.rawgaps
  self
end

#slice(index, len = 1) ⇒ Object Also known as: []

call-seq:

slice(position)   -> jig
slice(range)      -> jig
slice(start, len) -> jig

Extracts parts of a jig. The indexing scheme for jigs accounts for contents and gaps as follows:

     1    3       <- gaps
+----+----+----+
|    |    |    |
+----+----+----+
  0    2    4     <- contents

Each indexible element of the contents is itself a list of zero or more objects. A jig with n gaps will always have n + 1 content lists.

When called with a single integer (pos), slice returns the indexed item (a gap or a content list) as a jig.

j = Jig.new(0, :alpha, 'z')
j.slice(0)                   # => #<Jig: [0]>
j.slice(1)                   # => #<Jig: [:alpha]>
j.slice(2)                   # => #<Jig: ['z']>

When called with a range or a start position and length, slice extracts the indexed items and returns them as a new jig.

j = Jig.new(0, :alpha, 'z')
j.slice(0..1)                # => #<Jig: [0, :alpha]>
j.slice(1..2)                # => #<Jig: [:alpha, 'z']>
j.slice(0,1)                 # => #<Jig: [0]>
j.slice(0,2)                 # => #<Jig: [0, :alpha]>

Negative array indices are respected:

j = Jig.new(0, :alpha, 'z')
j.slice(-1)                  # => #<Jig: ['z']>
j.slice(-2..-1)              # => #<Jig: [:alpha, 'z']>
j.slice(-2, 1)               # => #<Jig: [:alpha]>


466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
# File 'lib/jig.rb', line 466

def slice(index, len=1)
  if Range === index
    if index.exclude_end?
      first, last = index.begin, index.end - 1
    else
      first, last = index.begin, index.end 
    end
  else
    first, last = index, index + len - 1
  end

  # Adjust for negative indices.
  first = 2*contents.size + first - 1 if first < 0
  last = 2*contents.size + last - 1 if last < 0
  first_adjust, last_adjust = first % 2, last % 2

  j = Jig.new
  j.rawgaps = rawgaps[((first - first_adjust)/2)...((last + last_adjust)/2)]
  j.contents = contents[((first + first_adjust)/2)..((last - last_adjust)/2)]

  j.contents.unshift([]) if first_adjust.nonzero?
  j.contents.push([]) if last_adjust.nonzero?
  if !j.contents or !j.rawgaps
    raise ArgumentError, "index #{index} out of range"
  end
  j
end

#split(*args) ⇒ Object

call-seq:

split(pattern=$;, [limit])

With no arguments, the jig is split at the gap positions into an array of strings. If arguments are provided, the jig is rendered to a string by #to_s and the result of String#split (with the arguments) is returned.



643
644
645
646
647
648
649
# File 'lib/jig.rb', line 643

def split(*args)
  if args.empty?
    contents.map { |c| c.join }
  else
    to_s.split(*args)
  end
end

#syntaxObject

Construct and array that represents the syntax of the jig including its gaps. Gaps are only distinguished by their name and not their semantics.



316
317
318
# File 'lib/jig.rb', line 316

def syntax
  contents.zip(gaps).flatten
end

#to_jigObject

Returns self.

j = Jig.new
j.equal?(j.to_jig)          # => true


417
418
419
# File 'lib/jig.rb', line 417

def to_jig
  self
end

#to_sObject Also known as: to_str

A string is constructed by concatenating the contents of the jig. Gaps are effectively considered null strings. Any procs in the jig are evaluated, the results converted to a string via to_s. All other objects are converted to strings via to_s.



823
824
825
# File 'lib/jig.rb', line 823

def to_s
  contents.flatten.join
end