Class: MadderLib::Builder
- Inherits:
-
Object
- Object
- MadderLib::Builder
- Includes:
- Enumerable
- Defined in:
- lib/madderlib/builder.rb
Overview
Builder
A builder object for MadderLib sentences.
The builder is constructed to include all of the Phrases (rules) for building a sentence.
Traditionally this is done through KernelMethods shortcuts:
builder = madderlib :id do
say 'through Kernel'
...
end
This can also be done through standard construction
builder = MadderLib::Builder.new(:id) do
say('through construction')
...
end
Each time you invoke the builder using one of the following methods, its Phrases are executed from scratch using a new build context:
-
sentence : returns the resulting words as a composite String
-
words : returns a list of all the resulting words
-
each_words : iterates through each of the resulting words
You can clone an existing builder. The resulting object is ‘deeply’ cloned.
You can extend or append additional rules to an existing builder.
builder.extend { say 'something more'; ... }
You can add multiple setup and teardown blocks to the builder, which provide access to the active build context.
All of the other features of the Builder involve management of and dispatching to the current Phrase.
Defined Under Namespace
Instance Attribute Summary collapse
-
#id ⇒ Object
readonly
The (optional) id of the Builder.
-
#meta ⇒ Object
readonly
A Hash of arbitrary meta-data for the Builder.
-
#phrase_ids ⇒ Object
readonly
An Array containing the id from each Phrase which has one (they’re optional).
-
#phrases ⇒ Object
readonly
An Array of each Phrase (rule) within the Builder.
Instance Method Summary collapse
-
#[](k) ⇒ Object
Provides convenient access to the meta Hash.
-
#[]=(k, v) ⇒ Object
Provides convenient access to the meta Hash.
-
#after(ref, id = nil) ⇒ Object
Allocates a phrase which is said after another Phrase.
-
#alternately(*args, &block) ⇒ Object
(also: #or)
A shorthand method for phrase.alternately .
-
#an(id) ⇒ Object
(also: #a)
Allocates another Phrase, where an id is required.
-
#and_then(id = nil) ⇒ Object
(also: #also, #and, #then)
Allocates another Phrase.
-
#anytime(id = nil) ⇒ Object
(also: #anywhere)
Allocates a Phrase which will be said anywhere.
-
#append(&block) ⇒ Object
(also: #extend)
Executes the block provided within the context of the Builder instance.
-
#before(ref, id = nil) ⇒ Object
Allocates a phrase which is said before another Phrase.
-
#clone(id = nil) ⇒ Object
Creates a deep-clone of the builder.
-
#each_word(data = nil) ⇒ Object
(also: #each)
Iterates through each of the words resulting from execution of the Builder.
-
#first(id = nil) ⇒ Object
Allocates a Phrase which will be said first, relative to any existing Phrases.
-
#initialize(id = nil, &block) ⇒ Builder
constructor
Constructs a new Builder.
-
#last(id = nil) ⇒ Object
(also: #lastly)
Allocates a Phrase which will be said last, relative to any existing Phrases.
-
#phrase ⇒ Object
(also: #it)
Returns the current Phrase.
-
#say(*args, &block) ⇒ Object
(also: #says)
A shorthand method for and_then.say .
-
#sentence(*args, &block) ⇒ Object
(also: #to_s)
Returns the composite sentence resulting from execution of the Builder.
-
#setup(*args, &block) ⇒ Object
Adds a setup block to the builder.
-
#teardown(*args, &block) ⇒ Object
Adds a teardown block to the builder.
-
#to_sequencer ⇒ Object
(also: #validate)
:nodoc:.
-
#words(data = nil, &block) ⇒ Object
(also: #to_a)
Returns the array of words resulting from execution of the Builder.
Constructor Details
#initialize(id = nil, &block) ⇒ Builder
Constructs a new Builder. The id is optional. When a block is provided, it is used to extend the new (empty) Builder
The new Builder is not automatically added to the active Grammar. That is the responsibility of the caller
Examples:
builder = MadderLib::Builder.new
builder.id.should be_nil
builder.should have(0).phrases
builder = MadderLib::Builder.new :id
builder.id.should equal(:id)
builder.should have(0).phrases
builder = MadderLib::Builder.new do
say 'no id'
end
builder.id.should be_nil
builder.sentence.should eql('no id')
builder = MadderLib::Builder.new :id do
say {|context| context.builder.id }
end
builder.sentence.should eql('id')
76 77 78 79 80 81 82 83 84 85 |
# File 'lib/madderlib/builder.rb', line 76 def initialize(id=nil, &block) @id = id @phrases, @phrase_ids = [], [] @ordered, @depends = [], [] @setup, @teardown = [], [] @meta = {} self.extend &block if block_given? self end |
Instance Attribute Details
#id ⇒ Object (readonly)
The (optional) id of the Builder
41 42 43 |
# File 'lib/madderlib/builder.rb', line 41 def id @id end |
#meta ⇒ Object (readonly)
A Hash of arbitrary meta-data for the Builder. It is reserved for custom developer logic; the Builder doesn’t consider its meta-data
48 49 50 |
# File 'lib/madderlib/builder.rb', line 48 def @meta end |
#phrase_ids ⇒ Object (readonly)
An Array containing the id from each Phrase which has one (they’re optional)
45 46 47 |
# File 'lib/madderlib/builder.rb', line 45 def phrase_ids @phrase_ids end |
#phrases ⇒ Object (readonly)
An Array of each Phrase (rule) within the Builder
43 44 45 |
# File 'lib/madderlib/builder.rb', line 43 def phrases @phrases end |
Instance Method Details
#[](k) ⇒ Object
237 238 239 |
# File 'lib/madderlib/builder.rb', line 237 def [](k) @meta[k] end |
#[]=(k, v) ⇒ Object
Provides convenient access to the meta Hash.
See: []
244 245 246 |
# File 'lib/madderlib/builder.rb', line 244 def []=(k, v) @meta[k] = v end |
#after(ref, id = nil) ⇒ Object
Allocates a phrase which is said after another Phrase. The resulting words are inserted immediately after the referenced Phrase.
This phrase would of course be followed by any subsequent after Phrase referenced against the same id. Think of it as adding layers to an onion.
If the referenced Phrase is never said, due to conditionals / odds / etc, the dependent Phrase will not be said. This of course cascades throughout the dependency chain.
The referenced Phrase must exist, by id. However, that is not checked until execution (eg. not during build). You can test your completed Builder using the validate method.
Examples:
builder = MadderLib::Builder.new do
an(:always).says 'always'
a(:sometimes).says('sometimes').if {|context| context.builder[:sometimes] == true }
after(:always).say 'after-always'
after(:sometimes, :depends).say 'after-sometimes'
after(:depends).say 'depends'
end
builder.sentence.should eql('always after-always')
builder[:sometimes] = true
builder.sentence.should eql('always after-always sometimes after-sometimes depends')
433 434 435 |
# File 'lib/madderlib/builder.rb', line 433 def after(ref, id=nil) depends and_then(id), :after, ref end |
#alternately(*args, &block) ⇒ Object Also known as: or
A shorthand method for phrase.alternately . The Phrase#alternately method is invoked against the current Phrase with the arguments provided
An Error will be raised if there is no current Phrase. It’s an easy condition to recover from, but it’s bad use of the syntax.
Examples:
builder = MadderLib::Builder.new do
says 'word'
alternately.says :symbol
end
builder.or.say { 'lambda' }
builder.should have(1).phrases
builder.phrase.should have(3).instructions
%w{ word symbol lambda}.include?(builder.sentence).should be_true
470 471 472 473 |
# File 'lib/madderlib/builder.rb', line 470 def alternately(*args, &block) raise Error, "there is no active phrase. start one with 'say'" unless self.phrase self.phrase.or *args, &block end |
#an(id) ⇒ Object Also known as: a
302 303 304 |
# File 'lib/madderlib/builder.rb', line 302 def an(id) and_then id end |
#and_then(id = nil) ⇒ Object Also known as: also, and, then
282 283 284 285 286 |
# File 'lib/madderlib/builder.rb', line 282 def and_then(id=nil) add_id id @phrases << Phrase.new(self, id) @phrases.last end |
#anytime(id = nil) ⇒ Object Also known as: anywhere
Allocates a Phrase which will be said anywhere. It’s position will be random (though not first or last, except when there is no alternative). The Phrase will only appear once; you may want to make the Phrase recur as well
Examples:
builder = MadderLib::Builder.new do
say 'top'
say 'here'
say 'there'
say 'bottom'
end
builder.anywhere.say 'anywhere'
words = builder.words
words.should have(5).words
words.find_all {|word| word == 'anywhere'}.should have(1).word
builder.it.recurs(2)
words = builder.words
words.should have(6).words
words.find_all {|word| word == 'anywhere'}.should have(2).word
368 369 370 371 372 |
# File 'lib/madderlib/builder.rb', line 368 def anytime(id=nil) add_id id @phrases << AnytimePhrase.new(self, id) ordered self.phrase, :anytime end |
#append(&block) ⇒ Object Also known as: extend
Executes the block provided within the context of the Builder instance. This provides easy contextual access to say, or, first, anytime, and all other instance methods.
Examples:
builder = MadderLib::Builder.new { say 'construct' }
builder.append { say 'extended' }
builder.extend { say 'appending' }
builder.words.should eql(%w{ construct extended appending })
95 96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/madderlib/builder.rb', line 95 def append(&block) raise Error, 'extending block is required' unless block_given? # evaluate in our context # available in scope # closure locals # static methods, if you specify self.class # instance variables and methods # unavailable # any methods in closure scope (sorry, Tennessee) self.instance_eval &block self end |
#before(ref, id = nil) ⇒ Object
Allocates a phrase which is said before another Phrase. The resulting words are inserted immediately before the referenced Phrase.
This phrase would of course be preceded by any subsequent before Phrase referenced against the same id. Think of it as adding layers to an onion.
If the referenced Phrase is never said, due to conditionals / odds / etc, the dependent Phrase will not be said. This of course cascades throughout the dependency chain.
The referenced Phrase must exist, by id. However, that is not checked until execution (eg. not during build). You can test your completed Builder using the validate method.
Examples:
builder = MadderLib::Builder.new do
an(:always).says 'always'
a(:sometimes).says('sometimes').if {|context| context.builder[:sometimes] == true }
before(:always).say 'before-always'
before(:sometimes, :depends).say 'before-sometimes'
before(:depends).say 'depends'
end
builder.sentence.should eql('before-always always')
builder[:sometimes] = true
builder.sentence.should eql('before-always always depends before-sometimes sometimes')
402 403 404 |
# File 'lib/madderlib/builder.rb', line 402 def before(ref, id=nil) depends and_then(id), :before, ref end |
#clone(id = nil) ⇒ Object
Creates a deep-clone of the builder. You can extend the new builder’s Phrases, or add new setup blocks, without impacting the original.
Note that the two builders will share the same original Phrase list. If you modify one of them behind-the-scenes, that change will be shared by both builders.
Examples:
original = MadderLib::Builder.new do
[:meta] = :original
say 'original'
and_then(:shared).say('initial').likely(1)
end
cloned = original.clone
cloned[:meta] = :cloned
cloned.extend { say 'cloned' }
done = :original
cloned.setup { done = :cloned }
shared = original.phrases.find {|phrase| phrase.id == :shared }
shared.instructions.first.words << 'added'
original[:meta].should equal(:original)
original.sentence.should eql('original initial added')
done.should equal(:original)
cloned[:meta].should equal(:cloned)
cloned.sentence.should eql('original initial added cloned')
done.should equal(:cloned)
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 |
# File 'lib/madderlib/builder.rb', line 140 def clone(id=nil) o = super() # deeper copy @phrases = @phrases.clone @phrase_ids = @phrase_ids.clone @ordered = @ordered.clone @depends = @depends.clone @setup = @setup.clone @teardown = @teardown.clone @meta = @meta.clone # don't want two of them floating around with the same id o.instance_variable_set :@id, id # put it into the grammar # most importantly, grammar = Grammar.get_instance grammar.add o if grammar.builders.include?(self) o end |
#each_word(data = nil) ⇒ Object Also known as: each
Iterates through each of the words resulting from execution of the Builder.
An optional Hash of Context data can be provided. It is merged into Context#data before the Builder is executed
482 483 484 485 |
# File 'lib/madderlib/builder.rb', line 482 def each_word(data=nil) # from our words self.words(data).each {|word| yield word } end |
#first(id = nil) ⇒ Object
Allocates a Phrase which will be said first, relative to any existing Phrases. An optional Phrase id can be provided
This phrase would of course be preceded by any subsequent first Phrase
Examples:
builder = MadderLib::Builder.new do
say 'something'
first.say 'say'
end
builder.sentence.should eql('say something')
builder.first.say 'first'
builder.sentence.should eql('first say something')
323 324 325 |
# File 'lib/madderlib/builder.rb', line 323 def first(id=nil) ordered and_then(id), :first end |
#last(id = nil) ⇒ Object Also known as: lastly
Allocates a Phrase which will be said last, relative to any existing Phrases. An optional Phrase id can be provided
This phrase would of course be followed by any subsequent last Phrase
Examples:
builder = MadderLib::Builder.new do
last.say 'said'
say 'something'
end
builder.sentence.should eql('something said')
builder.last.say 'last'
builder.sentence.should eql('something said last')
341 342 343 |
# File 'lib/madderlib/builder.rb', line 341 def last(id=nil) ordered and_then(id), :last end |
#phrase ⇒ Object Also known as: it
Returns the current Phrase.
Examples:
builder = MadderLib::Builder.new do
say 'yes'
phrase.if {|context| context.builder[:activated] == true }
it.repeat(3)
end
builder.should have(1).phrases
builder.phrase.should have(1).instructions
builder.should have(0).words
builder[:activated] = true
builder.sentence.should eql('yes yes yes')
266 267 268 |
# File 'lib/madderlib/builder.rb', line 266 def phrase @phrases.last end |
#say(*args, &block) ⇒ Object Also known as: says
A shorthand method for and_then.say . A new Phrase is allocated (without an id), and then Phrase#say method is invoked with the arguments provided.
Examples:
builder = MadderLib::Builder.new do
says 'word'
say :symbol
say { 'lambda' }
end
builder.should have(3).phrases
builder.sentence.should eql('word symbol lambda')
449 450 451 |
# File 'lib/madderlib/builder.rb', line 449 def say(*args, &block) and_then.say *args, &block end |
#sentence(*args, &block) ⇒ Object Also known as: to_s
Returns the composite sentence resulting from execution of the Builder. It’s really just a shortcut for words.join .
An optional separator String can be provided. The default separator is a single space
An optional Hash of Context data can be provided. It is merged into Context#data before the Phrase rules are executed
An optional block can be provided. It will be invoked before the Phrase rules are executed The block can either take no arguments, or a Context.
Examples:
builder = MadderLib::Builder.new do
says 'word'
say :symbol, [:with, :hierarchy]
say { 'lambda' }
end
builder.sentence.should eql('word symbol with hierarchy lambda')
540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 |
# File 'lib/madderlib/builder.rb', line 540 def sentence(*args, &block) # argument scan sep, data = ' ', nil args.each do |arg| if String === arg # separator sep = arg elsif Hash === arg # context data data = arg end end self.words(data, &block).join(sep) end |
#setup(*args, &block) ⇒ Object
Adds a setup block to the builder.
The block is executed before the builder invokes its Phrases (rules). The block can either take no arguments, or a Context.
Subsequent blocks are executed in the order provided. If you provide :first
as an argument, the block will occur prior to any existing blocks. It would of course be preceded by any subsequent block which says that it is :first
.
Examples:
builder = MadderLib::Builder.new do
say {|context| context[:setup] }
end
builder.setup {|context| context[:setup] << 2 }
builder.setup {|context| context[:setup] << 3 }
builder.setup(:first) {|context| context[:setup] = [1] }
builder.sentence.should eql('1 2 3')
182 183 184 185 186 187 188 189 190 191 192 193 |
# File 'lib/madderlib/builder.rb', line 182 def setup(*args, &block) Context.validate(block) # ordering if args.include?(:first) @setup.unshift block else @setup.push block end self end |
#teardown(*args, &block) ⇒ Object
Adds a teardown block to the builder.
The block is executed after the builder has invoked its Phrases (rules). The block can either take no arguments, or a Context.
Subsequent blocks are executed in the order provided. If you provide :first
as an argument, the block will occur prior to any existing blocks. It would of course be preceded by any subsequent block which says that it is :first
.
Examples:
builder = MadderLib::Builder.new do
say 'teardown'
end
markers = []
builder.teardown {|context| markers << 2 }
builder.teardown {|context| markers << 3 }
builder.teardown(:first) {|context| markers = [1] }
builder.sentence.should eql('teardown')
markers.should eql([1, 2, 3])
215 216 217 218 219 220 221 222 223 224 225 226 |
# File 'lib/madderlib/builder.rb', line 215 def teardown(*args, &block) Context.validate(block) # ordering if args.include?(:first) @teardown.unshift block else @teardown.push block end self end |
#to_sequencer ⇒ Object Also known as: validate
:nodoc:
560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 |
# File 'lib/madderlib/builder.rb', line 560 def to_sequencer #:nodoc: # general ordering sequence = [] map = {} @phrases.each do |phrase| sequence << phrase unless self.ordered?(phrase) || self.depends?(phrase) map[phrase.id] = phrase if phrase.id end # specified ordering # anytimes anytimes = [] @ordered.each do |o| case o.type when :first # before all other firsts sequence.unshift o.phrase when :last # after all other lasts sequence.push o.phrase when :anytime # guarantee valid references phrase = o.phrase [phrase.before, phrase.after].each do |ref| raise Error, "no such phrase : #{ref.inspect}" unless (!ref) || map[ref] end anytimes << phrase else raise Error, "unknown ordering : #{o.type.inspect}" end end befores, afters = {}, {} @depends.each do |o| ref = o.ref raise Error, "no such phrase : #{ref.inspect}" unless map[ref] case o.type when :before phrases = befores[ref] befores[ref] = (phrases = []) unless phrases # before all other befores phrases.unshift o.phrase when :after phrases = afters[ref] afters[ref] = (phrases = []) unless phrases # after all other afters phrases.push o.phrase else raise Error, "unknown dependency : #{o.type.inspect}" end end Sequencer.new(self, sequence, map.keys, { :anytime => anytimes, :before => befores, :after => afters, :setup => @setup, :teardown => @teardown }) end |
#words(data = nil, &block) ⇒ Object Also known as: to_a
Returns the array of words resulting from execution of the Builder.
An optional Hash of Context data can be provided. It is merged into Context#data before the Phrase rules are executed
An optional block can be provided. It will be invoked before the Phrase rules are executed The block can either take no arguments, or a Context.
All Phrase rules are applied. Each word in the Array is a String. The resulting Array is flattened (vs. any Array hierarchies in the ruleset)
Examples:
builder = MadderLib::Builder.new do
says 'word'
say :symbol, [:with, :hierarchy]
say { 'lambda' }
end
builder.words.should eql(%w{ word symbol with hierarchy lambda })
508 509 510 511 512 513 514 515 516 517 |
# File 'lib/madderlib/builder.rb', line 508 def words(data=nil, &block) # words from a sequencer # pass on the context data # pass on the block, to pull in the context # a new Sequencer each time # TODO: optimize # dirty flag is hard since phrases is exposed # hashsum? clone of last known phrases PLUS dirty flag? self.to_sequencer.words data, &block end |