bud 0.9.7 → 0.9.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +13 -5
- data/History.txt +18 -0
- data/Rakefile +91 -0
- data/bin/budplot +7 -2
- data/docs/README.md +8 -17
- data/docs/cheat.md +1 -1
- data/docs/getstarted.md +95 -82
- data/docs/operational.md +3 -3
- data/examples/basics/paths.rb +2 -2
- data/examples/chat/chat_protocol.rb +1 -1
- data/lib/bud.rb +67 -51
- data/lib/bud/bud_meta.rb +64 -42
- data/lib/bud/collections.rb +29 -26
- data/lib/bud/executor/elements.rb +6 -6
- data/lib/bud/executor/join.rb +63 -52
- data/lib/bud/lattice-core.rb +5 -0
- data/lib/bud/monkeypatch.rb +38 -11
- data/lib/bud/rebl.rb +2 -2
- data/lib/bud/rewrite.rb +22 -11
- data/lib/bud/state.rb +2 -2
- data/lib/bud/storage/zookeeper.rb +7 -0
- data/lib/bud/version.rb +3 -0
- data/lib/bud/viz.rb +3 -3
- metadata +84 -82
checksums.yaml
CHANGED
@@ -1,7 +1,15 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
OTgyYTM2NWZkYmJlNjI4ODhkNWE3MGMwYmM0ZTFkYjFjMjQxYzdhOA==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
ZDI1ZDMzNWJiYTgzOGFhOTYxMmEyMjM4NjdmZDM5ZjFhYTBkYjg0Zg==
|
5
7
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
MTlhYWYxMTAyZDVhZWU4OTRmYjIwYjU5NzRlYzY1NDdmYzk3MWJjYTY1MDMw
|
10
|
+
OGRlYjNlMzc4MDZkOWZmOTdhZjRmNDYwMmYyYzk1MDU2NmUyNjdkMGU5NjBj
|
11
|
+
ZjY4NzYwYjU3MzBlOTlmZjUyMTgwMGEyYjdmNmNlZGMwNmU4MTU=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
MmJkNjZhNzNlMzdhOGJjZDk0ZWRiMWY2MTk2MWY4OWQ1MGI2NzA2NTMxZDk0
|
14
|
+
NTdjODdiMDg1MGY3ZTQ3YTBjZmU2ODE4NjYwMTY0NzRiNmRlMDI2YzYzZmZh
|
15
|
+
MGVkZWVmZDk5NDllMDFhYTMyMTg3YTUzZGE0NWVhYzgxNGVjYjc=
|
data/History.txt
CHANGED
@@ -1,3 +1,21 @@
|
|
1
|
+
== 0.9.8 / ???
|
2
|
+
|
3
|
+
* Fix bug in the stratification algorithm
|
4
|
+
* Fix bug with insertions into stdio inside bootstrap blocks
|
5
|
+
* Improve error message when <~ applied to a collection type that doesn't
|
6
|
+
support it (#316)
|
7
|
+
* Fix rescan logic for Zookeeper-backed collections (#317)
|
8
|
+
* Fix rescan logic for chained negation operators in a single rule
|
9
|
+
* Support + operator for concatenating tuples
|
10
|
+
* More consistent behavior for #==, #eql?, and #hash methods on Bud tuple
|
11
|
+
objects (TupleStruct)
|
12
|
+
* Fix intermittent EM::ConnectionNotBound exceptions during the Bud shutdown
|
13
|
+
sequence
|
14
|
+
* Improve stratification algorithm to be slightly more aggressive; that is, we
|
15
|
+
can place certain rules in an earlier strata than we were previously
|
16
|
+
(partially fixes #277)
|
17
|
+
* Add Rakefile to simplify common development operations (Joel VanderWerf, #324)
|
18
|
+
|
1
19
|
== 0.9.7 / 2013-04-22
|
2
20
|
|
3
21
|
* Avoid raising an exception when Bud is used with Ruby 2.0. There isn't
|
data/Rakefile
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
|
4
|
+
PRJ = "bud"
|
5
|
+
|
6
|
+
def version
|
7
|
+
@version ||= begin
|
8
|
+
$LOAD_PATH.unshift 'lib'
|
9
|
+
require 'bud/version'
|
10
|
+
warn "Bud::VERSION not a string" unless Bud::VERSION.kind_of? String
|
11
|
+
Bud::VERSION
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def tag
|
16
|
+
@tag ||= "v#{version}"
|
17
|
+
end
|
18
|
+
|
19
|
+
desc "Run all tests"
|
20
|
+
task :test => "test:unit"
|
21
|
+
|
22
|
+
TESTS = FileList["test/tc_*.rb"]
|
23
|
+
SLOW_TESTS = %w{ test/tc_execmodes.rb }
|
24
|
+
|
25
|
+
namespace :test do
|
26
|
+
desc "Run unit tests"
|
27
|
+
Rake::TestTask.new :unit do |t|
|
28
|
+
t.libs << "lib"
|
29
|
+
t.ruby_opts = %w{ -C test }
|
30
|
+
t.test_files = TESTS.sub('test/', '')
|
31
|
+
### it would be better to make each tc_*.rb not depend on pwd
|
32
|
+
end
|
33
|
+
|
34
|
+
desc "Run quick unit tests"
|
35
|
+
Rake::TestTask.new :quick do |t|
|
36
|
+
t.libs << "lib"
|
37
|
+
t.ruby_opts = %w{ -C test }
|
38
|
+
t.test_files = TESTS.exclude(*SLOW_TESTS).sub('test/', '')
|
39
|
+
end
|
40
|
+
|
41
|
+
desc "Run quick non-zk unit tests"
|
42
|
+
Rake::TestTask.new :quick_no_zk do |t|
|
43
|
+
t.libs << "lib"
|
44
|
+
t.ruby_opts = %w{ -C test }
|
45
|
+
t.test_files = TESTS.
|
46
|
+
exclude('test/tc_zookeeper.rb').
|
47
|
+
exclude(*SLOW_TESTS).
|
48
|
+
sub('test/', '')
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
desc "Commit, tag, and push repo; build and push gem"
|
53
|
+
task :release => "release:is_new_version" do
|
54
|
+
require 'tempfile'
|
55
|
+
|
56
|
+
sh "gem build #{PRJ}.gemspec"
|
57
|
+
|
58
|
+
file = Tempfile.new "template"
|
59
|
+
begin
|
60
|
+
file.puts "release #{version}"
|
61
|
+
file.close
|
62
|
+
sh "git commit --allow-empty -a -v -t #{file.path}"
|
63
|
+
ensure
|
64
|
+
file.close unless file.closed?
|
65
|
+
file.unlink
|
66
|
+
end
|
67
|
+
|
68
|
+
sh "git tag #{tag}"
|
69
|
+
sh "git push"
|
70
|
+
sh "git push --tags"
|
71
|
+
|
72
|
+
sh "gem push #{tag}.gem"
|
73
|
+
end
|
74
|
+
|
75
|
+
namespace :release do
|
76
|
+
desc "Diff to latest release"
|
77
|
+
task :diff do
|
78
|
+
latest = `git describe --abbrev=0 --tags --match 'v*'`.chomp
|
79
|
+
sh "git diff #{latest}"
|
80
|
+
end
|
81
|
+
|
82
|
+
desc "Log to latest release"
|
83
|
+
task :log do
|
84
|
+
latest = `git describe --abbrev=0 --tags --match 'v*'`.chomp
|
85
|
+
sh "git log #{latest}.."
|
86
|
+
end
|
87
|
+
|
88
|
+
task :is_new_version do
|
89
|
+
abort "#{tag} exists; update version!" unless `git tag -l #{tag}`.empty?
|
90
|
+
end
|
91
|
+
end
|
data/bin/budplot
CHANGED
@@ -21,6 +21,10 @@ def make_instance(mods)
|
|
21
21
|
# If we're given a single identifier that names a class, just return an
|
22
22
|
# instance of that class. Otherwise, define a bogus class that includes all
|
23
23
|
# the module names specified by the user and return an instance.
|
24
|
+
tmpserver = TCPServer.new('127.0.0.1', 0) # get a free port
|
25
|
+
default_params = {:dbm_dir => "/tmp/budplot_dbm_" + SecureRandom.uuid.to_s, :port => tmpserver.addr[1]}
|
26
|
+
|
27
|
+
|
24
28
|
mods.each do |m|
|
25
29
|
unless is_constant? m
|
26
30
|
puts "Error: unable to find definition for module or class \"#{m}\""
|
@@ -30,7 +34,7 @@ def make_instance(mods)
|
|
30
34
|
mod_klass = eval m
|
31
35
|
if mod_klass.class == Class
|
32
36
|
if mods.length == 1
|
33
|
-
return mod_klass.new
|
37
|
+
return mod_klass.new(default_params)
|
34
38
|
else
|
35
39
|
puts "Error: cannot intermix class \"#{mod_klass}\" with modules"
|
36
40
|
exit
|
@@ -50,7 +54,7 @@ def make_instance(mods)
|
|
50
54
|
]
|
51
55
|
class_def = def_lines.flatten.join("\n")
|
52
56
|
eval(class_def)
|
53
|
-
f =FooBar.new
|
57
|
+
f = FooBar.new(default_params)
|
54
58
|
3.times{ f.tick }
|
55
59
|
f
|
56
60
|
end
|
@@ -220,6 +224,7 @@ end
|
|
220
224
|
modules = []
|
221
225
|
ARGV.each do |arg|
|
222
226
|
if File.exists? arg
|
227
|
+
arg = File.expand_path arg
|
223
228
|
require arg
|
224
229
|
else
|
225
230
|
modules << arg
|
data/docs/README.md
CHANGED
@@ -6,28 +6,19 @@ Welcome to the documentation for *Bud*, a prototype of Bloom under development.
|
|
6
6
|
The documents here are organized to be read in any order, but you might like to
|
7
7
|
try the following:
|
8
8
|
|
9
|
-
* [intro.md
|
10
|
-
* [getstarted.md
|
9
|
+
* [intro](intro.md): A brief introduction to Bud and Bloom.
|
10
|
+
* [getstarted](getstarted.md): A quickstart to teach you basic Bloom
|
11
11
|
concepts, the use of `rebl` interactive terminal, and the embedding of Bloom
|
12
12
|
code in Ruby via the `Bud` module.
|
13
|
-
* [operational.md
|
13
|
+
* [operational](operational.md): An operational view of Bloom, to provide
|
14
14
|
a more detailed model of how Bloom code is evaluated by Bud.
|
15
|
-
* [cheat.md
|
16
|
-
* [modules.md
|
17
|
-
* [ruby\_hooks.md
|
15
|
+
* [cheat](cheat.md): Full documentation of the language constructs in a concise "cheat sheet" style.
|
16
|
+
* [modules](modules.md): An overview of Bloom's modularity features.
|
17
|
+
* [ruby_hooks](ruby\_hooks.md): Bud module methods that allow you to
|
18
18
|
interact with the Bud evaluator from other Ruby threads.
|
19
|
-
* [visualizations.md
|
19
|
+
* [visualizations](visualizations.md): Overview of the `budvis` and
|
20
20
|
`budplot` tools for visualizing Bloom program analyses.
|
21
|
-
* [bfs.md
|
22
|
-
|
23
|
-
[intro]: /docs/intro.md
|
24
|
-
[getstarted]: /docs/getstarted.md
|
25
|
-
[operational]: /docs/operational.md
|
26
|
-
[cheat]: /docs/cheat.md
|
27
|
-
[modules]: /docs/modules.md
|
28
|
-
[ruby_hooks]: /docs/ruby_hooks.md
|
29
|
-
[visualizations]: /docs/visualizations.md
|
30
|
-
[bfs]: /docs/bfs.md
|
21
|
+
* [bfs](bfs.md): A walkthrough of the Bloom distributed filesystem.
|
31
22
|
|
32
23
|
In addition, the [bud-sandbox](http://github.com/bloom-lang/bud-sandbox) GitHub
|
33
24
|
repository contains lots of useful libraries and example programs built using
|
data/docs/cheat.md
CHANGED
@@ -194,7 +194,7 @@ implicit map:
|
|
194
194
|
end
|
195
195
|
|
196
196
|
## BudCollection-Specific Methods ##
|
197
|
-
`bc.schema`: returns the schema of `bc` (Hash of key column names => non-key column names). Note that for channels, this omits the location specifier (<tt>@</tt>).<br>
|
197
|
+
`bc.schema`: returns the schema of `bc` (Hash of key column names => non-key column names; if no non-key columns, just an Array of key column names). Note that for channels, this omits the location specifier (<tt>@</tt>).<br>
|
198
198
|
|
199
199
|
`bc.cols`: returns the column names in `bc` as an Array<br>
|
200
200
|
|
data/docs/getstarted.md
CHANGED
@@ -66,9 +66,11 @@ Second, note that our Bud program's one statement merges the values on its right
|
|
66
66
|
### Tables and Scratches ###
|
67
67
|
Before we dive into writing server code, let's try a slightly more involved single-timestep example. Start up rebl again, and paste in the following:
|
68
68
|
|
69
|
-
|
70
|
-
|
71
|
-
|
69
|
+
``` ruby
|
70
|
+
table :clouds
|
71
|
+
clouds <= [[1, "Cirrus"], [2, "Cumulus"]]
|
72
|
+
stdio <~ clouds.inspected
|
73
|
+
```
|
72
74
|
|
73
75
|
Now tick your rebl, but don't quit yet.
|
74
76
|
|
@@ -128,8 +130,10 @@ Now that we've seen a bit of Bloom, we're ready to write our first interesting s
|
|
128
130
|
|
129
131
|
Even though we're getting ahead of ourselves, let's have a peek at the Bloom statements that implement the server in `examples/chat/chat_server.rb`:
|
130
132
|
|
131
|
-
|
132
|
-
|
133
|
+
``` ruby
|
134
|
+
nodelist <= connect { |c| [c.client, c.nick] }
|
135
|
+
mcast <~ (mcast * nodelist).pairs { |m,n| [n.key, m.val] }
|
136
|
+
```
|
133
137
|
|
134
138
|
That's it! There is one statement for each of the two sentences describing the behavior of the "basic idea" above. We'll go through these two statements in more detail shortly. But it's nice to see right away how concisely and naturally a Bloom program can fit our intuitive description of a distributed service.
|
135
139
|
|
@@ -137,14 +141,16 @@ That's it! There is one statement for each of the two sentences describing the
|
|
137
141
|
|
138
142
|
Now that we've satisfied our need to peek, let's take this a bit more methodically. First we need declarations for the various Bloom collections we'll be using. We put the declarations that are common to both client and server into file `examples/chat/chat_protocol.rb`:
|
139
143
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
144
|
+
``` ruby
|
145
|
+
module ChatProtocol
|
146
|
+
state do
|
147
|
+
channel :connect, [:@addr, :client] => [:nick]
|
148
|
+
channel :mcast
|
149
|
+
end
|
150
|
+
|
151
|
+
DEFAULT_ADDR = "localhost:12345"
|
152
|
+
end
|
153
|
+
```
|
148
154
|
|
149
155
|
This defines a [Ruby mixin module](http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_modules.html) called `ChatProtocol` that has a couple special Bloom features:
|
150
156
|
|
@@ -156,33 +162,34 @@ This defines a [Ruby mixin module](http://www.ruby-doc.org/docs/ProgrammingRuby/
|
|
156
162
|
|
157
163
|
Given this protocol (and the Ruby constant at the bottom), we're now ready to examine `examples/chat/chat_server.rb` in more detail:
|
158
164
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
class ChatServer
|
164
|
-
include Bud
|
165
|
-
include ChatProtocol
|
166
|
-
|
167
|
-
state { table :nodelist }
|
165
|
+
``` ruby
|
166
|
+
require 'rubygems'
|
167
|
+
require 'bud'
|
168
|
+
require_relative 'chat_protocol'
|
168
169
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
end
|
173
|
-
end
|
174
|
-
|
175
|
-
if ARGV.first
|
176
|
-
addr = ARGV.first
|
177
|
-
else
|
178
|
-
addr = ChatProtocol::DEFAULT_ADDR
|
179
|
-
end
|
170
|
+
class ChatServer
|
171
|
+
include Bud
|
172
|
+
include ChatProtocol
|
180
173
|
|
181
|
-
|
182
|
-
puts "Server address: #{ip}:#{port}"
|
183
|
-
program = ChatServer.new(:ip => ip, :port => port.to_i)
|
184
|
-
program.run
|
174
|
+
state { table :nodelist }
|
185
175
|
|
176
|
+
bloom do
|
177
|
+
nodelist <= connect { |c| [c.client, c.nick] }
|
178
|
+
mcast <~ (mcast * nodelist).pairs { |m,n| [n.key, m.val] }
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
if ARGV.first
|
183
|
+
addr = ARGV.first
|
184
|
+
else
|
185
|
+
addr = ChatProtocol::DEFAULT_ADDR
|
186
|
+
end
|
187
|
+
|
188
|
+
ip, port = addr.split(":")
|
189
|
+
puts "Server address: #{ip}:#{port}"
|
190
|
+
program = ChatServer.new(:ip => ip, :port => port.to_i)
|
191
|
+
program.run_fg
|
192
|
+
```
|
186
193
|
|
187
194
|
The first few lines get the appropriate Ruby classes and modules loaded via `require`. We then define the ChatServer class which mixes in the `Bud` module and the ChatProtocol module we looked at above. Then we have another `state` block that declares one additional collection, the `nodelist` table.
|
188
195
|
|
@@ -190,14 +197,18 @@ With those preliminaries aside, we have our first `bloom` block, which is how Bl
|
|
190
197
|
|
191
198
|
The first is pretty simple:
|
192
199
|
|
193
|
-
|
200
|
+
``` ruby
|
201
|
+
nodelist <= connect { |c| [c.client, c.nick] }
|
202
|
+
```
|
194
203
|
|
195
204
|
This says that whenever messages arrive on the channel named "connect", the client address and user-provided nickname should be instantaneously merged into the table "nodelist", which will store them persistently. Note that nodelist has a \[key/val\] pair structure, so it is suitable for storing pairs of (IP address, nickname).
|
196
205
|
|
197
206
|
The next Bloom statement is more complex. Remember the description in the "basic idea" at the beginning of this section: the server needs to accept inbound chat messages from clients and forward them to other clients.
|
198
207
|
|
199
|
-
|
200
|
-
|
208
|
+
``` ruby
|
209
|
+
mcast <~ (mcast * nodelist).pairs { |m,n| [n.key, m.val] }
|
210
|
+
```
|
211
|
+
|
201
212
|
The first thing to note is the lhs and operator in this statement. We are merging items (asynchronously, of course!) into the mcast channel, where they will be sent to their eventual destination.
|
202
213
|
|
203
214
|
The rhs is our first introduction to the `*` operator of Bloom collections, and the `pairs` method after it. You can think of the `*` operator as "all-pairs": it produces a Bloom collection containing all pairs of mcast and nodelist items. The `pairs` method iterates through these pairs, passing them through a code block via the block arguments `m` and `n`. Finally, for each such pair the block produces an item containing the `key` attribute of the nodelist item, and the `val` attribute of the mcast item. This is structured as a proper \[address, val\] entry to be merged back into the mcast channel. Putting this together, this statement *multicasts inbound payloads on the mcast channel to all nodes in the chat*.
|
@@ -205,7 +216,7 @@ The rhs is our first introduction to the `*` operator of Bloom collections, and
|
|
205
216
|
The remaining lines of plain Ruby simply instantiate and run the ChatServer class (which includes the `Bud` module) using an ip and port given on the command line (or the default from ChatProtocol.rb).
|
206
217
|
|
207
218
|
#### `*`'s and Clouds ####
|
208
|
-
You can think of
|
219
|
+
You can think of our use of the `*` operator on the rhs of the second statement in a few different ways:
|
209
220
|
|
210
221
|
* If you're familiar with event-loop programming, this implements an *event handler* for messages on the mcast channel: whenever an mcast message arrives, this handler performs lookups in the nodelist table to form new messages. (It is easy to add "filters" to these handlers as arguments to `pairs`.) The resulting messages are dispatched via the mcast channel accordingly. This is a very common pattern in Bloom programs: handling channel messages via lookups in a table.
|
211
222
|
|
@@ -218,49 +229,51 @@ Given our understanding of the server, the client should be pretty simple. It n
|
|
218
229
|
|
219
230
|
And here's the code:
|
220
231
|
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
def initialize(nick, server, opts={})
|
230
|
-
@nick = nick
|
231
|
-
@server = server
|
232
|
-
super opts
|
233
|
-
end
|
234
|
-
|
235
|
-
bootstrap do
|
236
|
-
connect <~ [[@server, ip_port, @nick]]
|
237
|
-
end
|
238
|
-
|
239
|
-
bloom do
|
240
|
-
mcast <~ stdio do |s|
|
241
|
-
[@server, [ip_port, @nick, Time.new.strftime("%I:%M.%S"), s.line]]
|
242
|
-
end
|
243
|
-
|
244
|
-
stdio <~ mcast { |m| [pretty_print(m.val)] }
|
245
|
-
end
|
246
|
-
|
247
|
-
# format chat messages with timestamp on the right of the screen
|
248
|
-
def pretty_print(val)
|
249
|
-
str = val[1].to_s + ": " + (val[3].to_s || '')
|
250
|
-
pad = "(" + val[2].to_s + ")"
|
251
|
-
return str + " "*[66 - str.length,2].max + pad
|
252
|
-
end
|
253
|
-
end
|
232
|
+
``` ruby
|
233
|
+
require 'rubygems'
|
234
|
+
require 'bud'
|
235
|
+
require_relative 'chat_protocol'
|
236
|
+
|
237
|
+
class ChatClient
|
238
|
+
include Bud
|
239
|
+
include ChatProtocol
|
254
240
|
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
241
|
+
def initialize(nick, server, opts={})
|
242
|
+
@nick = nick
|
243
|
+
@server = server
|
244
|
+
super opts
|
245
|
+
end
|
246
|
+
|
247
|
+
bootstrap do
|
248
|
+
connect <~ [[@server, ip_port, @nick]]
|
249
|
+
end
|
250
|
+
|
251
|
+
bloom do
|
252
|
+
mcast <~ stdio do |s|
|
253
|
+
[@server, [ip_port, @nick, Time.new.strftime("%I:%M.%S"), s.line]]
|
259
254
|
end
|
260
255
|
|
261
|
-
|
262
|
-
|
263
|
-
|
256
|
+
stdio <~ mcast { |m| [pretty_print(m.val)] }
|
257
|
+
end
|
258
|
+
|
259
|
+
# format chat messages with timestamp on the right of the screen
|
260
|
+
def pretty_print(val)
|
261
|
+
str = val[1].to_s + ": " + (val[3].to_s || '')
|
262
|
+
pad = "(" + val[2].to_s + ")"
|
263
|
+
return str + " "*[66 - str.length,2].max + pad
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
if ARGV.length == 2
|
268
|
+
server = ARGV[1]
|
269
|
+
else
|
270
|
+
server = ChatProtocol::DEFAULT_ADDR
|
271
|
+
end
|
272
|
+
|
273
|
+
puts "Server address: #{server}"
|
274
|
+
program = ChatClient.new(ARGV[0], server, :stdin => $stdin)
|
275
|
+
program.run_fg
|
276
|
+
```
|
264
277
|
|
265
278
|
The ChatClient class has a typical Ruby `initialize` method that sets up two local instance variables: one for this client's nickname, and another for the 'IP:port' address string for the server. It then calls the initializer of the Bud superclass passing along a hash of options.
|
266
279
|
|
@@ -293,4 +306,4 @@ In this section we saw a number of features that we missed in our earlier single
|
|
293
306
|
* **the * operator and pairs method**: the way to combine items from multiple collections.
|
294
307
|
|
295
308
|
# The Big Picture and the Details #
|
296
|
-
Now that you've seen some working Bloom code, hopefully you're ready to delve deeper. The [README](README.md) provides links to places you can go for more information. Have fun and [stay in touch](http://groups.google.com/group/bloom-lang)!
|
309
|
+
Now that you've seen some working Bloom code, hopefully you're ready to delve deeper. The [README](README.md) provides links to places you can go for more information. Have fun and [stay in touch](http://groups.google.com/group/bloom-lang)!
|