greenletters 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ # The list of files that should be ignored by Mr Bones.
2
+ # Lines that start with '#' are comments.
3
+ #
4
+ # A .gitignore file can be used instead by setting it as the ignore
5
+ # file in your Rakefile:
6
+ #
7
+ # Bones {
8
+ # ignore_file '.gitignore'
9
+ # }
10
+ #
11
+ # For a project with a C extension, the following would be a good set of
12
+ # exclude patterns (uncomment them if you want to use them):
13
+ # *.[oa]
14
+ # *~
15
+ announcement.txt
16
+ coverage
17
+ doc
18
+ pkg
19
+ /examples/cucumber/greenletters.log
data/History.txt ADDED
@@ -0,0 +1,4 @@
1
+ == 0.0.1 / 2010-07-19
2
+
3
+ * 1 major enhancement
4
+ * Birthday!
data/README.org ADDED
@@ -0,0 +1,107 @@
1
+ #+Title: Greenletters README
2
+ #+AUTHOR: Avdi Grimm
3
+ #+EMAIL: avdi@avdi.org
4
+
5
+ # Configuration:
6
+ #+STARTUP: odd
7
+ #+STARTUP: hi
8
+ #+STARTUP: hidestars
9
+
10
+
11
+ * Synopsis
12
+
13
+ #+begin_src ruby
14
+ require 'greenletters'
15
+ adv = Greenletters::Process.new("adventure", :transcript => $stdout)
16
+ adv.on(:output, /welcome to adventure/i) do |process, match_data|
17
+ adv << "no\n"
18
+ end
19
+
20
+ puts "Starting adventure..."
21
+ adv.start!
22
+ adv.wait_for(:output, /you are standing at the end of a road/i)
23
+ adv << "east\n"
24
+ adv.wait_for(:output, /inside a building/i)
25
+ adv << "quit\n"
26
+ adv.wait_for(:output, /really want to quit/i)
27
+ adv << "yes\n"
28
+ adv.wait_for(:exit)
29
+ puts "Adventure has exited."
30
+ #+end_src
31
+
32
+ Or, in Cucumber format:
33
+
34
+ #+BEGIN_SRC
35
+ Given process activity is logged to "greenletters.log"
36
+ Given a process "adventure" from command "adventure"
37
+ Given I reply "no" to output "Would you like instructions?" from process "adventure"
38
+ Given I reply "yes" to output "Do you really want to quit" from process "adventure"
39
+ When I execute the process "adventure"
40
+ Then I should see the following output from process "adventure":
41
+ """
42
+ You are standing at the end of a road before a small brick building.
43
+ Around you is a forest. A small stream flows out of the building and
44
+ down a gully.
45
+ """
46
+ When I enter "east" into process "adventure"
47
+ Then I should see the following output from process "adventure":
48
+ """
49
+ You are inside a building, a well house for a large spring.
50
+ """
51
+ #+END_SRC
52
+
53
+ * What
54
+
55
+ Greenletters is a console interaction automation library similar to [[http://directory.fsf.org/project/expect/][GNU
56
+ Expect]]. You can use it to script interactions with command-line programs.
57
+
58
+ * Why
59
+ Because Ruby's built-in expect.rb is pretty underpowered and I wanted to drive
60
+ command-line applications from Ruby, not TCL.
61
+
62
+ * Who
63
+ Greenletters is by [[mailto:avdi@avdi.org][Avdi Grimm]].
64
+
65
+ * Where
66
+ http://github.com/avdi/greenletters
67
+
68
+ * How
69
+ Greenletters uses the pty.rb library under the covers to create a UNIX
70
+ pseudoterminal under Ruby's control. Of course, this means that it is
71
+ *NIX-only; Windows users need not apply.
72
+
73
+ The advantage of using a PTY is that *any* output - inclding output written to
74
+ the console instead of STDOUT/STDERR - will be captured by Greenletters.
75
+
76
+ * Cucumber
77
+ To use the Cucumber steps in your own feature files, put the following in your env.rb:
78
+
79
+ #+BEGIN_SRC ruby
80
+ require 'greenletters'
81
+ require 'greenletters/cucumber_steps'
82
+ #+END_SRC
83
+
84
+ * LICENSE
85
+
86
+ (The MIT License)
87
+
88
+ Copyright (c) 2010 Avdi Grimm
89
+
90
+ Permission is hereby granted, free of charge, to any person obtaining
91
+ a copy of this software and associated documentation files (the
92
+ 'Software'), to deal in the Software without restriction, including
93
+ without limitation the rights to use, copy, modify, merge, publish,
94
+ distribute, sublicense, and/or sell copies of the Software, and to
95
+ permit persons to whom the Software is furnished to do so, subject to
96
+ the following conditions:
97
+
98
+ The above copyright notice and this permission notice shall be
99
+ included in all copies or substantial portions of the Software.
100
+
101
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
102
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
103
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
104
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
105
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
106
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
107
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,30 @@
1
+
2
+ begin
3
+ require 'bones'
4
+ rescue LoadError
5
+ abort '### Please install the "bones" gem ###'
6
+ end
7
+
8
+ task :default => 'test:run'
9
+ task 'gem:release' => 'test:run'
10
+
11
+ Bones {
12
+ name 'greenletters'
13
+ authors 'Avdi Grimm'
14
+ email 'avdi@avdi.org'
15
+ url 'http://github.com/avdi/greenletters'
16
+ ignore_file '.gitignore'
17
+ readme_file 'README.org'
18
+
19
+ summary 'A Ruby console automation framework a la Expect'
20
+
21
+ description <<-END
22
+ Greenletterrs is a console automation framework, similar to the classic
23
+ utility Expect. You give it a command to execute, and tell it which outputs
24
+ or events to expect and how to respond to them.
25
+
26
+ Greenletters also includes a set of Cucumber steps which simplify the task
27
+ of spcifying interactive command-line applications.
28
+ END
29
+ }
30
+
data/bin/greenletters ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.expand_path(
4
+ File.join(File.dirname(__FILE__), %w[.. lib greenletters]))
5
+
6
+ # Put your code here
7
+
@@ -0,0 +1,30 @@
1
+ # This demo interacts with the classic Collossal Cave Adventure game. To install
2
+ # the game on Debian-based systems (Ubuntu, etc), execute:
3
+ #
4
+ # sudo apt-get install bsdgames
5
+ #
6
+ $:.unshift(File.expand_path('../lib', File.dirname(__FILE__)))
7
+ require 'greenletters'
8
+ require 'logger'
9
+
10
+ logger = ::Logger.new($stdout)
11
+ logger.level = ::Logger::INFO
12
+ # logger.level = ::Logger::DEBUG
13
+ adv = Greenletters::Process.new("adventure",
14
+ :logger => logger,
15
+ :transcript => $stdout)
16
+ adv.on(:output, /welcome to adventure/i) do |process, match_data|
17
+ adv << "no\n"
18
+ end
19
+
20
+ puts "Starting aadventure..."
21
+ adv.start!
22
+ adv.wait_for(:output, /you are standing at the end of a road/i)
23
+ adv << "east\n"
24
+ adv.wait_for(:output, /inside a building/i)
25
+ adv << "quit\n"
26
+ adv.wait_for(:output, /really want to quit/i)
27
+ adv << "yes\n"
28
+ adv.wait_for(:exit)
29
+ puts "Adventure has exited."
30
+
@@ -0,0 +1,67 @@
1
+ Feature: play adventure
2
+ As a nerd
3
+ I want to play a text adventure game
4
+ Because I'm old-skool
5
+
6
+ Scenario: play first few rooms (named process)
7
+ Given process activity is logged to "greenletters.log"
8
+ Given a process "adventure" from command "adventure"
9
+ Given I reply "no" to output "Would you like instructions?" from process "adventure"
10
+ Given I reply "yes" to output "Do you really want to quit" from process "adventure"
11
+ When I execute the process "adventure"
12
+ Then I should see the following output from process "adventure":
13
+ """
14
+ You are standing at the end of a road before a small brick building.
15
+ Around you is a forest. A small stream flows out of the building and
16
+ down a gully.
17
+ """
18
+ When I enter "east" into process "adventure"
19
+ Then I should see the following output from process "adventure":
20
+ """
21
+ You are inside a building, a well house for a large spring.
22
+ """
23
+ When I enter "west" into process "adventure"
24
+ Then I should see the following output from process "adventure":
25
+ """
26
+ You're at end of road again.
27
+ """
28
+ When I enter "south" into process "adventure"
29
+ Then I should see the following output from process "adventure":
30
+ """
31
+ You are in a valley in the forest beside a stream tumbling along a
32
+ rocky bed.
33
+ """
34
+ When I enter "quit" into process "adventure"
35
+ Then the process "adventure" should exit succesfully
36
+
37
+ Scenario: play first few rooms (default process)
38
+ Given process activity is logged to "greenletters.log"
39
+ Given a process from command "adventure"
40
+ Given I reply "no" to output "Would you like instructions?"
41
+ Given I reply "yes" to output "Do you really want to quit"
42
+ When I execute the process
43
+ Then I should see the following output:
44
+ """
45
+ You are standing at the end of a road before a small brick building.
46
+ Around you is a forest. A small stream flows out of the building and
47
+ down a gully.
48
+ """
49
+ When I enter "east"
50
+ Then I should see the following output:
51
+ """
52
+ You are inside a building, a well house for a large spring.
53
+ """
54
+ When I enter "west"
55
+ Then I should see the following output:
56
+ """
57
+ You're at end of road again.
58
+ """
59
+ When I enter "south"
60
+ Then I should see the following output:
61
+ """
62
+ You are in a valley in the forest beside a stream tumbling along a
63
+ rocky bed.
64
+ """
65
+ When I enter "quit"
66
+ Then the process should exit succesfully
67
+
@@ -0,0 +1,251 @@
1
+ D, [2010-07-18T09:07:00.322981 #4796] DEBUG -- : added trigger on output matching /Would\s+you\s+like\s+instructions\?/
2
+ D, [2010-07-18T09:07:00.324028 #4796] DEBUG -- : added trigger on output matching /Do\s+you\s+really\s+want\s+to\s+quit/
3
+ D, [2010-07-18T09:07:00.324701 #4796] DEBUG -- : installing end marker handler for __GREENLETTERS_PROCESS_ENDED__
4
+ D, [2010-07-18T09:07:00.324808 #4796] DEBUG -- : prepended trigger on output matching /__GREENLETTERS_PROCESS_ENDED__/
5
+ D, [2010-07-18T09:07:00.324875 #4796] DEBUG -- : executing /usr/bin/ruby1.8 -C /home/avdi/dev/greenletters/examples/cucumber -e system(*["adventure"]) -e puts("__GREENLETTERS_PROCESS_ENDED__") -e gets -e exit $?.exitstatus
6
+ D, [2010-07-18T09:07:00.325021 #4796] DEBUG -- : command environment:
7
+ {"LIBGL_DRIVERS_PATH"=>"/usr/lib/fglrx/dri:/usr/lib32/fglrx/dri", "GDM_LANG"=>"en_US.utf8", "LANGUAGE"=>"", "KONSOLE_DBUS_SERVICE"=>":1.10", "LOGNAME"=>"avdi", "GTK_MODULES"=>"canberra-gtk-module", "DBUS_SESSION_BUS_ADDRESS"=>"unix:abstract=/tmp/dbus-ysd8AY9I4g,guid=d9670f4428aeffbcad7a4cf94c40af03", "SPEECHD_PORT"=>"7560", "PATH"=>"/home/avdi/bin:/home/avdi/share/bin:/home/avdi/.gem/ruby/1.8/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games", "USER"=>"avdi", "GNOME_KEYRING_PID"=>"2330", "LANG"=>"en_US.utf8", "DEFAULTS_PATH"=>"/usr/share/gconf/gnome.default.path", "WINDOWID"=>"23068726", "XDG_CONFIG_DIRS"=>"/etc/xdg/xdg-gnome:/etc/xdg", "ORBIT_SOCKETDIR"=>"/tmp/orbit-avdi", "XDG_DATA_DIRS"=>"/usr/share/gnome:/usr/local/share/:/usr/share/", "GNOME_DESKTOP_SESSION_ID"=>"this-is-deprecated", "DISPLAY"=>":0.0", "PWD"=>"/home/avdi/dev/greenletters/examples/cucumber", "GDMSESSION"=>"gnome", "GDM_KEYBOARD_LAYOUT"=>"us", "COLORFGBG"=>"15;0", "XAUTHORITY"=>"/var/run/gdm/auth-for-avdi-GRGq1x/database", "SSH_AGENT_PID"=>"2389", "XDG_SESSION_COOKIE"=>"95da948d870c084a39cf95e74bcf1d12-1279307523.573384-2005890633", "TERM"=>"xterm", "PROFILEHOME"=>"", "SESSION_MANAGER"=>"local/petronius:@/tmp/.ICE-unix/2348,unix/petronius:/tmp/.ICE-unix/2348", "DESKTOP_AUTOSTART_ID"=>"10957e6313bf75a5e0127930752431450800000023480004", "SSH_AUTH_SOCK"=>"/tmp/keyring-5DVyII/ssh", "KONSOLE_DBUS_SESSION"=>"/Sessions/4", "GNOME_KEYRING_CONTROL"=>"/tmp/keyring-5DVyII", "DESKTOP_SESSION"=>"gnome", "USERNAME"=>"avdi", "MANDATORY_PATH"=>"/usr/share/gconf/gnome.mandatory.path", "HOME"=>"/home/avdi", "GPG_AGENT_INFO"=>"/tmp/gpg-5xymDi/S.gpg-agent:2390:1", "SHELL"=>"/bin/zsh", "SHLVL"=>"1", "OLDPWD"=>"/home/avdi/dev/greenletters/examples", "EDITOR"=>"/home/avdi/share/bin/emacs-newwindow", "GEM_EDITOR"=>"/home/avdi/share/bin/emacs-newwindow", "ZSH"=>"/home/avdi/.oh-my-zsh", "ZSH_THEME"=>"robbyrussell", "LSCOLORS"=>"Gxfxcxdxbxegedabagacad", "GREP_OPTIONS"=>"--color=auto", "GREP_COLOR"=>"1;32", "PAGER"=>"less", "LC_CTYPE"=>"en_US.UTF-8", "rvm_path"=>"/home/avdi/.rvm", "rvm_rubies_path"=>"/home/avdi/.rvm/rubies", "rvm_scripts_path"=>"/home/avdi/.rvm/scripts", "rvm_archives_path"=>"/home/avdi/.rvm/archives", "rvm_src_path"=>"/home/avdi/.rvm/src", "rvm_log_path"=>"/home/avdi/.rvm/log", "rvm_bin_path"=>"/home/avdi/.rvm/bin", "rvm_gems_path"=>"/home/avdi/.rvm/gems", "rvm_config_path"=>"/home/avdi/.rvm/config", "rvm_tmp_path"=>"/home/avdi/.rvm/tmp", "rvm_hooks_path"=>"/home/avdi/.rvm/hooks", "rvm_gems_cache_path"=>"/home/avdi/.rvm/gems/cache", "rvm_gemset_separator"=>"@", "rvm_version"=>"0.1.28", "_"=>"/home/avdi/.gem/ruby/1.8/bin/cucumber"}
8
+ D, [2010-07-18T09:07:00.337560 #4796] DEBUG -- : spawned pid 4801
9
+ D, [2010-07-18T09:07:00.338511 #4796] DEBUG -- : added trigger on output matching /You\s+are\s+standing\s+at\s+the\s+end\s+of\s+a\s+road\s+before\s+a\s+small\s+brick\s+building\.\s+Around\s+you\s+is\s+a\s+forest\.\s+A\s+small\s+stream\s+flows\s+out\s+of\s+the\s+building\s+and\s+down\s+a\s+gully\./
10
+ D, [2010-07-18T09:07:00.338569 #4796] DEBUG -- : waiting for output matching /You\s+are\s+standing\s+at\s+the\s+end\s+of\s+a\s+road\s+before\s+a\s+small\s+brick\s+building\.\s+Around\s+you\s+is\s+a\s+forest\.\s+A\s+small\s+stream\s+flows\s+out\s+of\s+the\s+building\s+and\s+down\s+a\s+gully\./
11
+ D, [2010-07-18T09:07:00.338608 #4796] DEBUG -- : select()
12
+ D, [2010-07-18T09:07:00.338693 #4796] DEBUG -- : select() on [[#<File:/dev/pts/7>], [], [#<File:/dev/pts/7>, #<File:/dev/pts/7>]]
13
+ D, [2010-07-18T09:07:00.338747 #4796] DEBUG -- : output ready #<File:/dev/pts/7>
14
+ D, [2010-07-18T09:07:00.338825 #4796] DEBUG -- :
15
+ <<
16
+ << Welcome to Adventure!! Would you like instructions?
17
+ D, [2010-07-18T09:07:00.338863 #4796] DEBUG -- : read 56 bytes
18
+ D, [2010-07-18T09:07:00.338910 #4796] DEBUG -- : checking output against output matching /__GREENLETTERS_PROCESS_ENDED__/
19
+ D, [2010-07-18T09:07:00.338966 #4796] DEBUG -- : matching /__GREENLETTERS_PROCESS_ENDED__/ against "\r\nWelcome to Adventure!! Would you like instructions?\r\n"
20
+ D, [2010-07-18T09:07:00.339007 #4796] DEBUG -- : no match
21
+ D, [2010-07-18T09:07:00.339043 #4796] DEBUG -- : checking output against output matching /Would\s+you\s+like\s+instructions\?/
22
+ D, [2010-07-18T09:07:00.339077 #4796] DEBUG -- : matching /Would\s+you\s+like\s+instructions\?/ against "\r\nWelcome to Adventure!! Would you like instructions?\r\n"
23
+ D, [2010-07-18T09:07:00.339117 #4796] DEBUG -- : matched /Would\s+you\s+like\s+instructions\?/
24
+ D, [2010-07-18T09:07:00.339168 #4796] DEBUG -- : match trigger output matching /Would\s+you\s+like\s+instructions\?/
25
+ D, [2010-07-18T09:07:00.339209 #4796] DEBUG -- : checking output against output matching /Do\s+you\s+really\s+want\s+to\s+quit/
26
+ D, [2010-07-18T09:07:00.339247 #4796] DEBUG -- : matching /Do\s+you\s+really\s+want\s+to\s+quit/ against "\r\n"
27
+ D, [2010-07-18T09:07:00.339281 #4796] DEBUG -- : no match
28
+ D, [2010-07-18T09:07:00.339316 #4796] DEBUG -- : checking output against output matching /You\s+are\s+standing\s+at\s+the\s+end\s+of\s+a\s+road\s+before\s+a\s+small\s+brick\s+building\.\s+Around\s+you\s+is\s+a\s+forest\.\s+A\s+small\s+stream\s+flows\s+out\s+of\s+the\s+building\s+and\s+down\s+a\s+gully\./
29
+ D, [2010-07-18T09:07:00.339350 #4796] DEBUG -- : matching /You\s+are\s+standing\s+at\s+the\s+end\s+of\s+a\s+road\s+before\s+a\s+small\s+brick\s+building\.\s+Around\s+you\s+is\s+a\s+forest\.\s+A\s+small\s+stream\s+flows\s+out\s+of\s+the\s+building\s+and\s+down\s+a\s+gully\./ against "\r\n"
30
+ D, [2010-07-18T09:07:00.339380 #4796] DEBUG -- : no match
31
+ D, [2010-07-18T09:07:00.339414 #4796] DEBUG -- : select()
32
+ D, [2010-07-18T09:07:00.339455 #4796] DEBUG -- : select() on [[#<File:/dev/pts/7>], [#<File:/dev/pts/7>], [#<File:/dev/pts/7>, #<File:/dev/pts/7>]]
33
+ D, [2010-07-18T09:07:00.339504 #4796] DEBUG -- : input ready #<File:/dev/pts/7>
34
+ D, [2010-07-18T09:07:00.339563 #4796] DEBUG -- :
35
+ >> no
36
+ D, [2010-07-18T09:07:00.339599 #4796] DEBUG -- : wrote 3 bytes
37
+ D, [2010-07-18T09:07:00.339631 #4796] DEBUG -- : select()
38
+ D, [2010-07-18T09:07:00.339673 #4796] DEBUG -- : select() on [[#<File:/dev/pts/7>], [], [#<File:/dev/pts/7>, #<File:/dev/pts/7>]]
39
+ D, [2010-07-18T09:07:00.359024 #4796] DEBUG -- : output ready #<File:/dev/pts/7>
40
+ D, [2010-07-18T09:07:00.359161 #4796] DEBUG -- :
41
+ << no
42
+ <<
43
+ << You are standing at the end of a road before a small brick building.
44
+ << Around you is a forest. A small stream flows out of the building and
45
+ << down a gully.
46
+ D, [2010-07-18T09:07:00.359200 #4796] DEBUG -- : read 162 bytes
47
+ D, [2010-07-18T09:07:00.359251 #4796] DEBUG -- : checking output against output matching /__GREENLETTERS_PROCESS_ENDED__/
48
+ D, [2010-07-18T09:07:00.359299 #4796] DEBUG -- : matching /__GREENLETTERS_PROCESS_ENDED__/ against "\r\nno\r\n\r\nYou are standing at the end of a road before a small brick building.\r\nAround you is a forest. A small stream flows out of the building and\r\ndown a gully.\r\n"
49
+ D, [2010-07-18T09:07:00.359334 #4796] DEBUG -- : no match
50
+ D, [2010-07-18T09:07:00.359368 #4796] DEBUG -- : checking output against output matching /Would\s+you\s+like\s+instructions\?/
51
+ D, [2010-07-18T09:07:00.359406 #4796] DEBUG -- : matching /Would\s+you\s+like\s+instructions\?/ against "\r\nno\r\n\r\nYou are standing at the end of a road before a small brick building.\r\nAround you is a forest. A small stream flows out of the building and\r\ndown a gully.\r\n"
52
+ D, [2010-07-18T09:07:00.359437 #4796] DEBUG -- : no match
53
+ D, [2010-07-18T09:07:00.359507 #4796] DEBUG -- : checking output against output matching /Do\s+you\s+really\s+want\s+to\s+quit/
54
+ D, [2010-07-18T09:07:00.359548 #4796] DEBUG -- : matching /Do\s+you\s+really\s+want\s+to\s+quit/ against "\r\nno\r\n\r\nYou are standing at the end of a road before a small brick building.\r\nAround you is a forest. A small stream flows out of the building and\r\ndown a gully.\r\n"
55
+ D, [2010-07-18T09:07:00.359585 #4796] DEBUG -- : no match
56
+ D, [2010-07-18T09:07:00.359621 #4796] DEBUG -- : checking output against output matching /You\s+are\s+standing\s+at\s+the\s+end\s+of\s+a\s+road\s+before\s+a\s+small\s+brick\s+building\.\s+Around\s+you\s+is\s+a\s+forest\.\s+A\s+small\s+stream\s+flows\s+out\s+of\s+the\s+building\s+and\s+down\s+a\s+gully\./
57
+ D, [2010-07-18T09:07:00.359664 #4796] DEBUG -- : matching /You\s+are\s+standing\s+at\s+the\s+end\s+of\s+a\s+road\s+before\s+a\s+small\s+brick\s+building\.\s+Around\s+you\s+is\s+a\s+forest\.\s+A\s+small\s+stream\s+flows\s+out\s+of\s+the\s+building\s+and\s+down\s+a\s+gully\./ against "\r\nno\r\n\r\nYou are standing at the end of a road before a small brick building.\r\nAround you is a forest. A small stream flows out of the building and\r\ndown a gully.\r\n"
58
+ D, [2010-07-18T09:07:00.359714 #4796] DEBUG -- : matched /You\s+are\s+standing\s+at\s+the\s+end\s+of\s+a\s+road\s+before\s+a\s+small\s+brick\s+building\.\s+Around\s+you\s+is\s+a\s+forest\.\s+A\s+small\s+stream\s+flows\s+out\s+of\s+the\s+building\s+and\s+down\s+a\s+gully\./
59
+ D, [2010-07-18T09:07:00.359754 #4796] DEBUG -- : match trigger output matching /You\s+are\s+standing\s+at\s+the\s+end\s+of\s+a\s+road\s+before\s+a\s+small\s+brick\s+building\.\s+Around\s+you\s+is\s+a\s+forest\.\s+A\s+small\s+stream\s+flows\s+out\s+of\s+the\s+building\s+and\s+down\s+a\s+gully\./
60
+ D, [2010-07-18T09:07:00.359790 #4796] DEBUG -- : unblocked
61
+ D, [2010-07-18T09:07:00.359829 #4796] DEBUG -- : trigger removed
62
+ D, [2010-07-18T09:07:00.361484 #4796] DEBUG -- : added trigger on output matching /You\s+are\s+inside\s+a\s+building,\s+a\s+well\s+house\s+for\s+a\s+large\s+spring\./
63
+ D, [2010-07-18T09:07:00.361546 #4796] DEBUG -- : waiting for output matching /You\s+are\s+inside\s+a\s+building,\s+a\s+well\s+house\s+for\s+a\s+large\s+spring\./
64
+ D, [2010-07-18T09:07:00.361588 #4796] DEBUG -- : select()
65
+ D, [2010-07-18T09:07:00.361658 #4796] DEBUG -- : select() on [[#<File:/dev/pts/7>], [#<File:/dev/pts/7>], [#<File:/dev/pts/7>, #<File:/dev/pts/7>]]
66
+ D, [2010-07-18T09:07:00.361728 #4796] DEBUG -- : input ready #<File:/dev/pts/7>
67
+ D, [2010-07-18T09:07:00.361820 #4796] DEBUG -- :
68
+ >> east
69
+ D, [2010-07-18T09:07:00.361878 #4796] DEBUG -- : wrote 5 bytes
70
+ D, [2010-07-18T09:07:00.361942 #4796] DEBUG -- : select()
71
+ D, [2010-07-18T09:07:00.362026 #4796] DEBUG -- : select() on [[#<File:/dev/pts/7>], [], [#<File:/dev/pts/7>, #<File:/dev/pts/7>]]
72
+ D, [2010-07-18T09:07:00.379006 #4796] DEBUG -- : output ready #<File:/dev/pts/7>
73
+ D, [2010-07-18T09:07:00.379142 #4796] DEBUG -- :
74
+ << east
75
+ <<
76
+ << You are inside a building, a well house for a large spring.
77
+ <<
78
+ << There are some keys on the ground here.
79
+ <<
80
+ << There is a shiny brass lamp nearby.
81
+ <<
82
+ << There is food here.
83
+ <<
84
+ << There is a bottle of water here.
85
+ D, [2010-07-18T09:07:00.379182 #4796] DEBUG -- : read 210 bytes
86
+ D, [2010-07-18T09:07:00.379240 #4796] DEBUG -- : checking output against output matching /__GREENLETTERS_PROCESS_ENDED__/
87
+ D, [2010-07-18T09:07:00.379288 #4796] DEBUG -- : matching /__GREENLETTERS_PROCESS_ENDED__/ against "\r\neast\r\n\r\nYou are inside a building, a well house for a large spring.\r\n\r\nThere are some keys on the ground here.\r\n\r\nThere is a shiny brass lamp nearby.\r\n\r\nThere is food here.\r\n\r\nThere is a bottle of water here.\r\n"
88
+ D, [2010-07-18T09:07:00.379321 #4796] DEBUG -- : no match
89
+ D, [2010-07-18T09:07:00.379355 #4796] DEBUG -- : checking output against output matching /Would\s+you\s+like\s+instructions\?/
90
+ D, [2010-07-18T09:07:00.379394 #4796] DEBUG -- : matching /Would\s+you\s+like\s+instructions\?/ against "\r\neast\r\n\r\nYou are inside a building, a well house for a large spring.\r\n\r\nThere are some keys on the ground here.\r\n\r\nThere is a shiny brass lamp nearby.\r\n\r\nThere is food here.\r\n\r\nThere is a bottle of water here.\r\n"
91
+ D, [2010-07-18T09:07:00.379455 #4796] DEBUG -- : no match
92
+ D, [2010-07-18T09:07:00.379497 #4796] DEBUG -- : checking output against output matching /Do\s+you\s+really\s+want\s+to\s+quit/
93
+ D, [2010-07-18T09:07:00.379549 #4796] DEBUG -- : matching /Do\s+you\s+really\s+want\s+to\s+quit/ against "\r\neast\r\n\r\nYou are inside a building, a well house for a large spring.\r\n\r\nThere are some keys on the ground here.\r\n\r\nThere is a shiny brass lamp nearby.\r\n\r\nThere is food here.\r\n\r\nThere is a bottle of water here.\r\n"
94
+ D, [2010-07-18T09:07:00.379594 #4796] DEBUG -- : no match
95
+ D, [2010-07-18T09:07:00.379640 #4796] DEBUG -- : checking output against output matching /You\s+are\s+inside\s+a\s+building,\s+a\s+well\s+house\s+for\s+a\s+large\s+spring\./
96
+ D, [2010-07-18T09:07:00.379699 #4796] DEBUG -- : matching /You\s+are\s+inside\s+a\s+building,\s+a\s+well\s+house\s+for\s+a\s+large\s+spring\./ against "\r\neast\r\n\r\nYou are inside a building, a well house for a large spring.\r\n\r\nThere are some keys on the ground here.\r\n\r\nThere is a shiny brass lamp nearby.\r\n\r\nThere is food here.\r\n\r\nThere is a bottle of water here.\r\n"
97
+ D, [2010-07-18T09:07:00.379758 #4796] DEBUG -- : matched /You\s+are\s+inside\s+a\s+building,\s+a\s+well\s+house\s+for\s+a\s+large\s+spring\./
98
+ D, [2010-07-18T09:07:00.379810 #4796] DEBUG -- : match trigger output matching /You\s+are\s+inside\s+a\s+building,\s+a\s+well\s+house\s+for\s+a\s+large\s+spring\./
99
+ D, [2010-07-18T09:07:00.379858 #4796] DEBUG -- : unblocked
100
+ D, [2010-07-18T09:07:00.379910 #4796] DEBUG -- : trigger removed
101
+ D, [2010-07-18T09:07:00.381659 #4796] DEBUG -- : added trigger on output matching /You're\s+at\s+end\s+of\s+road\s+again\./
102
+ D, [2010-07-18T09:07:00.381736 #4796] DEBUG -- : waiting for output matching /You're\s+at\s+end\s+of\s+road\s+again\./
103
+ D, [2010-07-18T09:07:00.381794 #4796] DEBUG -- : select()
104
+ D, [2010-07-18T09:07:00.381854 #4796] DEBUG -- : select() on [[#<File:/dev/pts/7>], [#<File:/dev/pts/7>], [#<File:/dev/pts/7>, #<File:/dev/pts/7>]]
105
+ D, [2010-07-18T09:07:00.381920 #4796] DEBUG -- : input ready #<File:/dev/pts/7>
106
+ D, [2010-07-18T09:07:00.382004 #4796] DEBUG -- :
107
+ >> west
108
+ D, [2010-07-18T09:07:00.382053 #4796] DEBUG -- : wrote 5 bytes
109
+ D, [2010-07-18T09:07:00.382096 #4796] DEBUG -- : select()
110
+ D, [2010-07-18T09:07:00.382157 #4796] DEBUG -- : select() on [[#<File:/dev/pts/7>], [], [#<File:/dev/pts/7>, #<File:/dev/pts/7>]]
111
+ D, [2010-07-18T09:07:00.397861 #4796] DEBUG -- : output ready #<File:/dev/pts/7>
112
+ D, [2010-07-18T09:07:00.398226 #4796] DEBUG -- :
113
+ << west
114
+ <<
115
+ << You're at end of road again.
116
+ D, [2010-07-18T09:07:00.398280 #4796] DEBUG -- : read 38 bytes
117
+ D, [2010-07-18T09:07:00.398344 #4796] DEBUG -- : checking output against output matching /__GREENLETTERS_PROCESS_ENDED__/
118
+ D, [2010-07-18T09:07:00.398412 #4796] DEBUG -- : matching /__GREENLETTERS_PROCESS_ENDED__/ against "\r\n\r\nThere are some keys on the ground here.\r\n\r\nThere is a shiny brass lamp nearby.\r\n\r\nThere is food here.\r\n\r\nThere is a bottle of water here.\r\nwest\r\n\r\nYou're at end of road again.\r\n"
119
+ D, [2010-07-18T09:07:00.398458 #4796] DEBUG -- : no match
120
+ D, [2010-07-18T09:07:00.398504 #4796] DEBUG -- : checking output against output matching /Would\s+you\s+like\s+instructions\?/
121
+ D, [2010-07-18T09:07:00.398556 #4796] DEBUG -- : matching /Would\s+you\s+like\s+instructions\?/ against "\r\n\r\nThere are some keys on the ground here.\r\n\r\nThere is a shiny brass lamp nearby.\r\n\r\nThere is food here.\r\n\r\nThere is a bottle of water here.\r\nwest\r\n\r\nYou're at end of road again.\r\n"
122
+ D, [2010-07-18T09:07:00.398598 #4796] DEBUG -- : no match
123
+ D, [2010-07-18T09:07:00.398643 #4796] DEBUG -- : checking output against output matching /Do\s+you\s+really\s+want\s+to\s+quit/
124
+ D, [2010-07-18T09:07:00.398695 #4796] DEBUG -- : matching /Do\s+you\s+really\s+want\s+to\s+quit/ against "\r\n\r\nThere are some keys on the ground here.\r\n\r\nThere is a shiny brass lamp nearby.\r\n\r\nThere is food here.\r\n\r\nThere is a bottle of water here.\r\nwest\r\n\r\nYou're at end of road again.\r\n"
125
+ D, [2010-07-18T09:07:00.398777 #4796] DEBUG -- : no match
126
+ D, [2010-07-18T09:07:00.398828 #4796] DEBUG -- : checking output against output matching /You're\s+at\s+end\s+of\s+road\s+again\./
127
+ D, [2010-07-18T09:07:00.398885 #4796] DEBUG -- : matching /You're\s+at\s+end\s+of\s+road\s+again\./ against "\r\n\r\nThere are some keys on the ground here.\r\n\r\nThere is a shiny brass lamp nearby.\r\n\r\nThere is food here.\r\n\r\nThere is a bottle of water here.\r\nwest\r\n\r\nYou're at end of road again.\r\n"
128
+ D, [2010-07-18T09:07:00.398959 #4796] DEBUG -- : matched /You're\s+at\s+end\s+of\s+road\s+again\./
129
+ D, [2010-07-18T09:07:00.399010 #4796] DEBUG -- : match trigger output matching /You're\s+at\s+end\s+of\s+road\s+again\./
130
+ D, [2010-07-18T09:07:00.399053 #4796] DEBUG -- : unblocked
131
+ D, [2010-07-18T09:07:00.399099 #4796] DEBUG -- : trigger removed
132
+ D, [2010-07-18T09:07:00.401301 #4796] DEBUG -- : added trigger on output matching /You\s+are\s+in\s+a\s+valley\s+in\s+the\s+forest\s+beside\s+a\s+stream\s+tumbling\s+along\s+a\s+rocky\s+bed\./
133
+ D, [2010-07-18T09:07:00.401391 #4796] DEBUG -- : waiting for output matching /You\s+are\s+in\s+a\s+valley\s+in\s+the\s+forest\s+beside\s+a\s+stream\s+tumbling\s+along\s+a\s+rocky\s+bed\./
134
+ D, [2010-07-18T09:07:00.401444 #4796] DEBUG -- : select()
135
+ D, [2010-07-18T09:07:00.401514 #4796] DEBUG -- : select() on [[#<File:/dev/pts/7>], [#<File:/dev/pts/7>], [#<File:/dev/pts/7>, #<File:/dev/pts/7>]]
136
+ D, [2010-07-18T09:07:00.401581 #4796] DEBUG -- : input ready #<File:/dev/pts/7>
137
+ D, [2010-07-18T09:07:00.401661 #4796] DEBUG -- :
138
+ >> south
139
+ D, [2010-07-18T09:07:00.401710 #4796] DEBUG -- : wrote 6 bytes
140
+ D, [2010-07-18T09:07:00.401754 #4796] DEBUG -- : select()
141
+ D, [2010-07-18T09:07:00.401810 #4796] DEBUG -- : select() on [[#<File:/dev/pts/7>], [], [#<File:/dev/pts/7>, #<File:/dev/pts/7>]]
142
+ D, [2010-07-18T09:07:00.418999 #4796] DEBUG -- : output ready #<File:/dev/pts/7>
143
+ D, [2010-07-18T09:07:00.419139 #4796] DEBUG -- :
144
+ << south
145
+ <<
146
+ << You are in a valley in the forest beside a stream tumbling along a
147
+ << rocky bed.
148
+ D, [2010-07-18T09:07:00.419189 #4796] DEBUG -- : read 89 bytes
149
+ D, [2010-07-18T09:07:00.419253 #4796] DEBUG -- : checking output against output matching /__GREENLETTERS_PROCESS_ENDED__/
150
+ D, [2010-07-18T09:07:00.419311 #4796] DEBUG -- : matching /__GREENLETTERS_PROCESS_ENDED__/ against "\r\nsouth\r\n\r\nYou are in a valley in the forest beside a stream tumbling along a\r\nrocky bed.\r\n"
151
+ D, [2010-07-18T09:07:00.419356 #4796] DEBUG -- : no match
152
+ D, [2010-07-18T09:07:00.419402 #4796] DEBUG -- : checking output against output matching /Would\s+you\s+like\s+instructions\?/
153
+ D, [2010-07-18T09:07:00.419449 #4796] DEBUG -- : matching /Would\s+you\s+like\s+instructions\?/ against "\r\nsouth\r\n\r\nYou are in a valley in the forest beside a stream tumbling along a\r\nrocky bed.\r\n"
154
+ D, [2010-07-18T09:07:00.419490 #4796] DEBUG -- : no match
155
+ D, [2010-07-18T09:07:00.419535 #4796] DEBUG -- : checking output against output matching /Do\s+you\s+really\s+want\s+to\s+quit/
156
+ D, [2010-07-18T09:07:00.419575 #4796] DEBUG -- : matching /Do\s+you\s+really\s+want\s+to\s+quit/ against "\r\nsouth\r\n\r\nYou are in a valley in the forest beside a stream tumbling along a\r\nrocky bed.\r\n"
157
+ D, [2010-07-18T09:07:00.419607 #4796] DEBUG -- : no match
158
+ D, [2010-07-18T09:07:00.419641 #4796] DEBUG -- : checking output against output matching /You\s+are\s+in\s+a\s+valley\s+in\s+the\s+forest\s+beside\s+a\s+stream\s+tumbling\s+along\s+a\s+rocky\s+bed\./
159
+ D, [2010-07-18T09:07:00.419680 #4796] DEBUG -- : matching /You\s+are\s+in\s+a\s+valley\s+in\s+the\s+forest\s+beside\s+a\s+stream\s+tumbling\s+along\s+a\s+rocky\s+bed\./ against "\r\nsouth\r\n\r\nYou are in a valley in the forest beside a stream tumbling along a\r\nrocky bed.\r\n"
160
+ D, [2010-07-18T09:07:00.419725 #4796] DEBUG -- : matched /You\s+are\s+in\s+a\s+valley\s+in\s+the\s+forest\s+beside\s+a\s+stream\s+tumbling\s+along\s+a\s+rocky\s+bed\./
161
+ D, [2010-07-18T09:07:00.419792 #4796] DEBUG -- : match trigger output matching /You\s+are\s+in\s+a\s+valley\s+in\s+the\s+forest\s+beside\s+a\s+stream\s+tumbling\s+along\s+a\s+rocky\s+bed\./
162
+ D, [2010-07-18T09:07:00.419828 #4796] DEBUG -- : unblocked
163
+ D, [2010-07-18T09:07:00.419862 #4796] DEBUG -- : trigger removed
164
+ D, [2010-07-18T09:07:00.462688 #4796] DEBUG -- : added trigger on exit with status 0
165
+ D, [2010-07-18T09:07:00.462995 #4796] DEBUG -- : waiting for exit with status 0
166
+ D, [2010-07-18T09:07:00.463056 #4796] DEBUG -- : select()
167
+ D, [2010-07-18T09:07:00.463119 #4796] DEBUG -- : select() on [[#<File:/dev/pts/7>], [#<File:/dev/pts/7>], [#<File:/dev/pts/7>, #<File:/dev/pts/7>]]
168
+ D, [2010-07-18T09:07:00.463187 #4796] DEBUG -- : input ready #<File:/dev/pts/7>
169
+ D, [2010-07-18T09:07:00.463264 #4796] DEBUG -- :
170
+ >> quit
171
+ D, [2010-07-18T09:07:00.463314 #4796] DEBUG -- : wrote 5 bytes
172
+ D, [2010-07-18T09:07:00.463357 #4796] DEBUG -- : select()
173
+ D, [2010-07-18T09:07:00.463412 #4796] DEBUG -- : select() on [[#<File:/dev/pts/7>], [], [#<File:/dev/pts/7>, #<File:/dev/pts/7>]]
174
+ D, [2010-07-18T09:07:00.479013 #4796] DEBUG -- : output ready #<File:/dev/pts/7>
175
+ D, [2010-07-18T09:07:00.479151 #4796] DEBUG -- :
176
+ << quit
177
+ <<
178
+ << Do you really want to quit now?
179
+ D, [2010-07-18T09:07:00.479201 #4796] DEBUG -- : read 41 bytes
180
+ D, [2010-07-18T09:07:00.479267 #4796] DEBUG -- : checking output against output matching /__GREENLETTERS_PROCESS_ENDED__/
181
+ D, [2010-07-18T09:07:00.479321 #4796] DEBUG -- : matching /__GREENLETTERS_PROCESS_ENDED__/ against "\r\nquit\r\n\r\nDo you really want to quit now?\r\n"
182
+ D, [2010-07-18T09:07:00.479365 #4796] DEBUG -- : no match
183
+ D, [2010-07-18T09:07:00.479412 #4796] DEBUG -- : checking output against output matching /Would\s+you\s+like\s+instructions\?/
184
+ D, [2010-07-18T09:07:00.479458 #4796] DEBUG -- : matching /Would\s+you\s+like\s+instructions\?/ against "\r\nquit\r\n\r\nDo you really want to quit now?\r\n"
185
+ D, [2010-07-18T09:07:00.479500 #4796] DEBUG -- : no match
186
+ D, [2010-07-18T09:07:00.479546 #4796] DEBUG -- : checking output against output matching /Do\s+you\s+really\s+want\s+to\s+quit/
187
+ D, [2010-07-18T09:07:00.479592 #4796] DEBUG -- : matching /Do\s+you\s+really\s+want\s+to\s+quit/ against "\r\nquit\r\n\r\nDo you really want to quit now?\r\n"
188
+ D, [2010-07-18T09:07:00.479642 #4796] DEBUG -- : matched /Do\s+you\s+really\s+want\s+to\s+quit/
189
+ D, [2010-07-18T09:07:00.479712 #4796] DEBUG -- : match trigger output matching /Do\s+you\s+really\s+want\s+to\s+quit/
190
+ D, [2010-07-18T09:07:00.479767 #4796] DEBUG -- : select()
191
+ D, [2010-07-18T09:07:00.479831 #4796] DEBUG -- : select() on [[#<File:/dev/pts/7>], [#<File:/dev/pts/7>], [#<File:/dev/pts/7>, #<File:/dev/pts/7>]]
192
+ D, [2010-07-18T09:07:00.479895 #4796] DEBUG -- : input ready #<File:/dev/pts/7>
193
+ D, [2010-07-18T09:07:00.479968 #4796] DEBUG -- :
194
+ >> yes
195
+ D, [2010-07-18T09:07:00.480017 #4796] DEBUG -- : wrote 4 bytes
196
+ D, [2010-07-18T09:07:00.480060 #4796] DEBUG -- : select()
197
+ D, [2010-07-18T09:07:00.480115 #4796] DEBUG -- : select() on [[#<File:/dev/pts/7>], [], [#<File:/dev/pts/7>, #<File:/dev/pts/7>]]
198
+ D, [2010-07-18T09:07:00.499047 #4796] DEBUG -- : output ready #<File:/dev/pts/7>
199
+ D, [2010-07-18T09:07:00.499209 #4796] DEBUG -- :
200
+ << yes
201
+ <<
202
+ << OK
203
+ <<
204
+ <<
205
+ <<
206
+ << You scored 32 out of a possible 350 using 4 turns.
207
+ <<
208
+ << You are obviously a rank amateur. Better luck next time.
209
+ << To achieve the next higher rating, you need 4 more points.
210
+ << __GREENLETTERS_PROCESS_ENDED__
211
+ D, [2010-07-18T09:07:00.499249 #4796] DEBUG -- : read 222 bytes
212
+ D, [2010-07-18T09:07:00.499298 #4796] DEBUG -- : checking output against output matching /__GREENLETTERS_PROCESS_ENDED__/
213
+ D, [2010-07-18T09:07:00.499345 #4796] DEBUG -- : matching /__GREENLETTERS_PROCESS_ENDED__/ against " now?\r\nyes\r\n\r\nOK\r\n\r\n\r\n\r\nYou scored 32 out of a possible 350 using 4 turns.\r\n\r\nYou are obviously a rank amateur. Better luck next time.\r\nTo achieve the next higher rating, you need 4 more points.\r\n__GREENLETTERS_PROCESS_ENDED__\r\n"
214
+ D, [2010-07-18T09:07:00.499382 #4796] DEBUG -- : matched /__GREENLETTERS_PROCESS_ENDED__/
215
+ D, [2010-07-18T09:07:00.499444 #4796] DEBUG -- : end marker found
216
+ D, [2010-07-18T09:07:00.499506 #4796] DEBUG -- : end marker expunged from output buffer
217
+ D, [2010-07-18T09:07:00.499540 #4796] DEBUG -- : acknowledging end marker
218
+ D, [2010-07-18T09:07:00.499582 #4796] DEBUG -- : match trigger output matching /__GREENLETTERS_PROCESS_ENDED__/
219
+ D, [2010-07-18T09:07:00.499622 #4796] DEBUG -- : checking output against output matching /Would\s+you\s+like\s+instructions\?/
220
+ D, [2010-07-18T09:07:00.499663 #4796] DEBUG -- : matching /Would\s+you\s+like\s+instructions\?/ against " now?\r\nyes\r\n\r\nOK\r\n\r\n\r\n\r\nYou scored 32 out of a possible 350 using 4 turns.\r\n\r\nYou are obviously a rank amateur. Better luck next time.\r\nTo achieve the next higher rating, you need 4 more points.\r\n"
221
+ D, [2010-07-18T09:07:00.499695 #4796] DEBUG -- : no match
222
+ D, [2010-07-18T09:07:00.499729 #4796] DEBUG -- : checking output against output matching /Do\s+you\s+really\s+want\s+to\s+quit/
223
+ D, [2010-07-18T09:07:00.499783 #4796] DEBUG -- : matching /Do\s+you\s+really\s+want\s+to\s+quit/ against " now?\r\nyes\r\n\r\nOK\r\n\r\n\r\n\r\nYou scored 32 out of a possible 350 using 4 turns.\r\n\r\nYou are obviously a rank amateur. Better luck next time.\r\nTo achieve the next higher rating, you need 4 more points.\r\n"
224
+ D, [2010-07-18T09:07:00.499828 #4796] DEBUG -- : no match
225
+ D, [2010-07-18T09:07:00.499865 #4796] DEBUG -- : flushing triggers matching Greenletters::OutputTrigger
226
+ D, [2010-07-18T09:07:00.499903 #4796] DEBUG -- : select()
227
+ D, [2010-07-18T09:07:00.499949 #4796] DEBUG -- : select() on [[#<File:/dev/pts/7>], [#<File:/dev/pts/7>], [#<File:/dev/pts/7>, #<File:/dev/pts/7>]]
228
+ D, [2010-07-18T09:07:00.499996 #4796] DEBUG -- : input ready #<File:/dev/pts/7>
229
+ D, [2010-07-18T09:07:00.500048 #4796] DEBUG -- :
230
+
231
+ D, [2010-07-18T09:07:00.500084 #4796] DEBUG -- : wrote 1 bytes
232
+ D, [2010-07-18T09:07:00.500116 #4796] DEBUG -- : select()
233
+ D, [2010-07-18T09:07:00.500175 #4796] DEBUG -- : select() on [[#<File:/dev/pts/7>], [], [#<File:/dev/pts/7>, #<File:/dev/pts/7>]]
234
+ D, [2010-07-18T09:07:00.509491 #4796] DEBUG -- : output ready #<File:/dev/pts/7>
235
+ D, [2010-07-18T09:07:00.509618 #4796] DEBUG -- :
236
+ <<
237
+ D, [2010-07-18T09:07:00.509668 #4796] DEBUG -- : read 2 bytes
238
+ D, [2010-07-18T09:07:00.509729 #4796] DEBUG -- : flushing triggers matching Greenletters::OutputTrigger
239
+ D, [2010-07-18T09:07:00.509778 #4796] DEBUG -- : select()
240
+ D, [2010-07-18T09:07:00.509838 #4796] DEBUG -- : select() on [[#<File:/dev/pts/7>], [], [#<File:/dev/pts/7>, #<File:/dev/pts/7>]]
241
+ D, [2010-07-18T09:07:00.509898 #4796] DEBUG -- : output ready #<File:/dev/pts/7>
242
+ D, [2010-07-18T09:07:00.510051 #4796] DEBUG -- : Errno::EIO caught
243
+ D, [2010-07-18T09:07:00.510106 #4796] DEBUG -- : waiting for child 4801 to die
244
+ D, [2010-07-18T09:07:00.523528 #4796] DEBUG -- : caught PTY::ChildExited
245
+ D, [2010-07-18T09:07:00.523800 #4796] DEBUG -- : collecting remaining output
246
+ D, [2010-07-18T09:07:00.523953 #4796] DEBUG -- : Input/output error - /dev/pts/7
247
+ D, [2010-07-18T09:07:00.524009 #4796] DEBUG -- : handling exit of process 4801
248
+ D, [2010-07-18T09:07:00.524070 #4796] DEBUG -- : checking exit against exit with status 0
249
+ D, [2010-07-18T09:07:00.524129 #4796] DEBUG -- : match trigger exit with status 0
250
+ D, [2010-07-18T09:07:00.524172 #4796] DEBUG -- : unblocked
251
+ D, [2010-07-18T09:07:00.524216 #4796] DEBUG -- : trigger removed
@@ -0,0 +1,4 @@
1
+ $:.unshift(File.expand_path('../../../lib', File.dirname(__FILE__)))
2
+ require 'greenletters'
3
+ require 'greenletters/cucumber_steps'
4
+
@@ -0,0 +1,68 @@
1
+ require 'cucumber'
2
+
3
+ module Greenletters
4
+ module CucumberHelpers
5
+ def greenletters_prepare_entry(text)
6
+ text.chomp + "\n"
7
+ end
8
+ def greenletters_massage_pattern(text)
9
+ Regexp.new(Regexp.escape(text.strip.tr_s(" \r\n\t", " ")).gsub('\ ', '\s+'))
10
+ end
11
+ end
12
+ end
13
+
14
+ World(Greenletters::CucumberHelpers)
15
+
16
+ Before do
17
+ @greenletters_process_table = Hash.new {|h,k|
18
+ raise "No such process defined: #{k}"
19
+ }
20
+ end
21
+
22
+ Given /^process activity is logged to "([^\"]*)"$/ do |filename|
23
+ logger = ::Logger.new(open(filename, 'w+'))
24
+ #logger.level = ::Logger::INFO
25
+ logger.level = ::Logger::DEBUG
26
+ @greenletters_process_log = logger
27
+ end
28
+
29
+ Given /^a process (?:"([^\"]*)" )?from command "([^\"]*)"$/ do |name, command|
30
+ name ||= "default"
31
+ options = {
32
+ }
33
+ options[:logger] = @greenletters_process_log if @greenletters_process_log
34
+ @greenletters_process_table[name] = Greenletters::Process.new(command, options)
35
+ end
36
+
37
+ Given /^I reply "([^\"]*)" to output "([^\"]*)"(?: from process "([^\"]*)")?$/ do
38
+ |reply, pattern, name|
39
+ name ||= "default"
40
+ pattern = greenletters_massage_pattern(pattern)
41
+ @greenletters_process_table[name].on(:output, pattern) do |process, match_data|
42
+ process << greenletters_prepare_entry(reply)
43
+ end
44
+ end
45
+
46
+ When /^I execute the process(?: "([^\"]*)")?$/ do |name|
47
+ name ||= "default"
48
+ @greenletters_process_table[name].start!
49
+ end
50
+
51
+ Then /^I should see the following output(?: from process "([^\"]*)")?:$/ do
52
+ |name, pattern|
53
+ name ||= "default"
54
+ pattern = greenletters_massage_pattern(pattern)
55
+ @greenletters_process_table[name].wait_for(:output, pattern)
56
+ end
57
+
58
+ When /^I enter "([^\"]*)"(?: into process "([^\"]*)")?$/ do
59
+ |input, name|
60
+ name ||= "default"
61
+ @greenletters_process_table[name] << greenletters_prepare_entry(input)
62
+ end
63
+
64
+ Then /^the process(?: "([^\"]*)")? should exit succesfully$/ do |name|
65
+ name ||= "default"
66
+ @greenletters_process_table[name].wait_for(:exit, 0)
67
+ end
68
+
@@ -0,0 +1,566 @@
1
+ require 'logger'
2
+ require 'pty'
3
+ require 'forwardable'
4
+ require 'stringio'
5
+ require 'shellwords'
6
+ require 'rbconfig'
7
+ require 'strscan'
8
+
9
+ # A better expect.rb
10
+ #
11
+ # Implementation note: because of the way PTY is implemented in Ruby, it is
12
+ # possible when executing a quick non-interactive command for PTY::ChildExited
13
+ # to be raised before ever getting input/output handles to the child
14
+ # process. Without an output handle, it's not possible to read any output the
15
+ # process produced. This is obviously undesirable, especially since when a
16
+ # command is unexpectedly quick and noninteractive it's usually because there
17
+ # was an error and you really want to be able to see what the problem was.
18
+ #
19
+ # Greenletters' solution to this problem is to wrap every command in a short
20
+ # script. The script executes the passed command and on termination, outputs an
21
+ # easily recognizable marker string. Then it waits for acknowledgment (a
22
+ # newline) before exiting. When Greenletters sees the marker string in the
23
+ # output, it automatically performs the acknowledgement and allows the child
24
+ # process to finish. By forcing the child process to wait for acknowledgement,
25
+ # we guarantee that the child will never exit before we have a chance to look at
26
+ # the output.
27
+ module Greenletters
28
+
29
+ # :stopdoc:
30
+ LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
31
+ PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
32
+ # :startdoc:
33
+
34
+ # Returns the version string for the library.
35
+ #
36
+ def self.version
37
+ @version ||= File.read(path('version.txt')).strip
38
+ end
39
+
40
+ # Returns the library path for the module. If any arguments are given,
41
+ # they will be joined to the end of the libray path using
42
+ # <tt>File.join</tt>.
43
+ #
44
+ def self.libpath( *args, &block )
45
+ rv = args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
46
+ if block
47
+ begin
48
+ $LOAD_PATH.unshift LIBPATH
49
+ rv = block.call
50
+ ensure
51
+ $LOAD_PATH.shift
52
+ end
53
+ end
54
+ return rv
55
+ end
56
+
57
+ # Returns the lpath for the module. If any arguments are given,
58
+ # they will be joined to the end of the path using
59
+ # <tt>File.join</tt>.
60
+ #
61
+ def self.path( *args, &block )
62
+ rv = args.empty? ? PATH : ::File.join(PATH, args.flatten)
63
+ if block
64
+ begin
65
+ $LOAD_PATH.unshift PATH
66
+ rv = block.call
67
+ ensure
68
+ $LOAD_PATH.shift
69
+ end
70
+ end
71
+ return rv
72
+ end
73
+
74
+ # Utility method used to require all files ending in .rb that lie in the
75
+ # directory below this file that has the same name as the filename passed
76
+ # in. Optionally, a specific _directory_ name can be passed in such that
77
+ # the _filename_ does not have to be equivalent to the directory.
78
+ #
79
+ def self.require_all_libs_relative_to( fname, dir = nil )
80
+ dir ||= ::File.basename(fname, '.*')
81
+ search_me = ::File.expand_path(
82
+ ::File.join(::File.dirname(fname), dir, '**', '*.rb'))
83
+
84
+ Dir.glob(search_me).reject{|fn| fn =~ /cucumber_steps.rb$/}.sort.each {|rb| require rb}
85
+ end
86
+
87
+ LogicError = Class.new(::Exception)
88
+ SystemError = Class.new(RuntimeError)
89
+ TimeoutError = Class.new(SystemError)
90
+ ClientError = Class.new(RuntimeError)
91
+ StateError = Class.new(ClientError)
92
+
93
+ # This class offers a pass-through << operator and saves the most recent 256
94
+ # bytes which have passed through.
95
+ class TranscriptHistoryBuffer
96
+ attr_reader :buffer
97
+
98
+ def initialize(transcript)
99
+ @buffer = String.new
100
+ @transcript = transcript
101
+ end
102
+
103
+ def <<(output)
104
+ @buffer << output
105
+ @transcript << output
106
+ length = [@buffer.length, 256].min
107
+ @buffer = @buffer[-length, length]
108
+ self
109
+ end
110
+ end
111
+
112
+ def Trigger(event, *args, &block)
113
+ klass = trigger_class_for_event(event)
114
+ klass.new(*args, &block)
115
+ end
116
+
117
+ def trigger_class_for_event(event)
118
+ ::Greenletters.const_get("#{event.to_s.capitalize}Trigger")
119
+ end
120
+
121
+ class Trigger
122
+ attr_accessor :time_to_live
123
+ attr_accessor :exclusive
124
+ attr_accessor :logger
125
+ attr_accessor :interruption
126
+
127
+ alias_method :exclusive?, :exclusive
128
+
129
+ def initialize(options={}, &block)
130
+ @block = block || lambda{}
131
+ @exclusive = options.fetch(:exclusive) { false }
132
+ @logger = ::Logger.new($stdout)
133
+ @interruption = :none
134
+ end
135
+
136
+ def call(process)
137
+ @block.call(process)
138
+ true
139
+ end
140
+ end
141
+
142
+ class OutputTrigger < Trigger
143
+ def initialize(pattern=//, options={}, &block)
144
+ super(options, &block)
145
+ @pattern = pattern
146
+ end
147
+
148
+ def to_s
149
+ "output matching #{@pattern.inspect}"
150
+ end
151
+
152
+ def call(process)
153
+ scanner = process.output_buffer
154
+ @logger.debug "matching #{@pattern.inspect} against #{scanner.rest.inspect}"
155
+ if scanner.scan_until(@pattern)
156
+ @logger.debug "matched #{@pattern.inspect}"
157
+ @block.call(process, scanner)
158
+ true
159
+ else
160
+ false
161
+ end
162
+ end
163
+ end
164
+
165
+ class TimeoutTrigger < Trigger
166
+ def to_s
167
+ "timeout"
168
+ end
169
+
170
+ def call(process)
171
+ @block.call(process, process.blocker)
172
+ true
173
+ end
174
+ end
175
+
176
+ class ExitTrigger < Trigger
177
+ attr_reader :pattern
178
+
179
+ def initialize(pattern=0, options={}, &block)
180
+ super(options, &block)
181
+ @pattern = pattern
182
+ end
183
+
184
+ def call(process)
185
+ if pattern === process.status.exitstatus
186
+ @block.call(process, process.status)
187
+ true
188
+ else
189
+ false
190
+ end
191
+ end
192
+
193
+ def to_s
194
+ "exit with status #{pattern}"
195
+ end
196
+ end
197
+
198
+ class UnsatisfiedTrigger < Trigger
199
+ def to_s
200
+ "unsatisfied wait"
201
+ end
202
+
203
+ def call(process)
204
+ @block.call(process, process.interruption, process.blocker)
205
+ true
206
+ end
207
+ end
208
+
209
+ class Process
210
+ END_MARKER = '__GREENLETTERS_PROCESS_ENDED__'
211
+
212
+ # Shamelessly stolen from Rake
213
+ RUBY_EXT =
214
+ ((Config::CONFIG['ruby_install_name'] =~ /\.(com|cmd|exe|bat|rb|sh)$/) ?
215
+ "" :
216
+ Config::CONFIG['EXEEXT'])
217
+ RUBY = File.join(
218
+ Config::CONFIG['bindir'],
219
+ Config::CONFIG['ruby_install_name'] + RUBY_EXT).
220
+ sub(/.*\s.*/m, '"\&"')
221
+
222
+ extend Forwardable
223
+ include ::Greenletters
224
+
225
+ attr_reader :command # Command to run in a subshell
226
+ attr_accessor :blocker # The Trigger currently being waited for, if any
227
+ attr_reader :input_buffer # Input waiting to be written to process
228
+ attr_reader :output_buffer # Output ready to be read from process
229
+ attr_reader :status # :not_started -> :running -> :ended -> :exited
230
+ attr_reader :cwd # Working directory for the command
231
+
232
+ def_delegators :input_buffer, :puts, :write, :print, :printf, :<<
233
+ def_delegators :output_buffer, :read, :readpartial, :read_nonblock, :gets,
234
+ :getline
235
+ def_delegators :blocker, :interruption, :interruption=
236
+
237
+ def initialize(*args)
238
+ options = if args.last.is_a?(Hash) then args.pop else {} end
239
+ @command = args
240
+ @triggers = []
241
+ @blocker = nil
242
+ @input_buffer = StringIO.new
243
+ @output_buffer = StringScanner.new("")
244
+ @env = options.fetch(:env) {{}}
245
+ @cwd = options.fetch(:cwd) {Dir.pwd}
246
+ @logger = options.fetch(:logger) {
247
+ l = ::Logger.new($stdout)
248
+ l.level = ::Logger::WARN
249
+ l
250
+ }
251
+ @state = :not_started
252
+ @shell = options.fetch(:shell) { '/bin/sh' }
253
+ @transcript = options.fetch(:transcript) {
254
+ t = Object.new
255
+ def t.<<(*)
256
+ # NOOP
257
+ end
258
+ t
259
+ }
260
+ @history = TranscriptHistoryBuffer.new(@transcript)
261
+ end
262
+
263
+ def on(event, *args, &block)
264
+ t = add_trigger(event, *args, &block)
265
+ end
266
+
267
+ def wait_for(event, *args, &block)
268
+ raise "Already waiting for #{blocker}" if blocker
269
+ t = add_blocking_trigger(event, *args, &block)
270
+ process_events
271
+ rescue
272
+ unblock!
273
+ triggers.delete(t)
274
+ raise
275
+ end
276
+
277
+ def add_trigger(event, *args, &block)
278
+ t = Trigger(event, *args, &block)
279
+ t.logger = @logger
280
+ triggers << t
281
+ @logger.debug "added trigger on #{t}"
282
+ t
283
+ end
284
+
285
+ def prepend_trigger(event, *args, &block)
286
+ t = Trigger(event, *args, &block)
287
+ t.logger = @logger
288
+ triggers.unshift(t)
289
+ @logger.debug "prepended trigger on #{t}"
290
+ t
291
+ end
292
+
293
+
294
+ def add_blocking_trigger(event, *args, &block)
295
+ t = add_trigger(event, *args, &block)
296
+ t.time_to_live = 1
297
+ @logger.debug "waiting for #{t}"
298
+ self.blocker = t
299
+ t
300
+ end
301
+
302
+ def start!
303
+ raise StateError, "Already started!" unless not_started?
304
+ @logger.debug "installing end marker handler for #{END_MARKER}"
305
+ prepend_trigger(:output, /#{END_MARKER}/, :exclusive => false, :time_to_live => 1) do |process, data|
306
+ handle_end_marker
307
+ end
308
+ handle_child_exit do
309
+ cmd = wrapped_command
310
+ @logger.debug "executing #{cmd.join(' ')}"
311
+ merge_environment(@env) do
312
+ @logger.debug "command environment:\n#{ENV.inspect}"
313
+ @output, @input, @pid = PTY.spawn(*cmd)
314
+ end
315
+ @state = :running
316
+ @logger.debug "spawned pid #{@pid}"
317
+ end
318
+ end
319
+
320
+ def flush_output_buffer!
321
+ @logger.debug "flushing output buffer"
322
+ @output_buffer.terminate
323
+ end
324
+
325
+ def alive?
326
+ ::Process.kill(0, @pid)
327
+ true
328
+ rescue Errno::ESRCH, Errno::ENOENT
329
+ false
330
+ end
331
+
332
+ def blocked?
333
+ @blocker
334
+ end
335
+
336
+ def running?
337
+ @state == :running
338
+ end
339
+
340
+ def not_started?
341
+ @state == :not_started
342
+ end
343
+
344
+ def exited?
345
+ @state == :exited
346
+ end
347
+
348
+ # Have we seen the end marker yet?
349
+ def ended?
350
+ @state == :ended
351
+ end
352
+
353
+ private
354
+
355
+ attr_reader :triggers
356
+
357
+ def wrapped_command
358
+ [RUBY,
359
+ '-C', cwd,
360
+ '-e', "system(*#{command.inspect})",
361
+ '-e', "puts(#{END_MARKER.inspect})",
362
+ '-e', "gets",
363
+ '-e', "exit $?.exitstatus"
364
+ ]
365
+ end
366
+
367
+ def process_events
368
+ raise StateError, "Process not started!" if not_started?
369
+ handle_child_exit do
370
+ while blocked?
371
+ @logger.debug "select()"
372
+ input_handles = input_buffer.string.empty? ? [] : [@input]
373
+ output_handles = [@output]
374
+ error_handles = [@input, @output]
375
+ @logger.debug "select() on #{[output_handles, input_handles, error_handles].inspect}"
376
+ ready_handles = IO.select(
377
+ output_handles, input_handles, error_handles, 1.0)
378
+ if ready_handles.nil?
379
+ process_timeout
380
+ else
381
+ ready_outputs, ready_inputs, ready_errors = *ready_handles
382
+ ready_errors.each do |handle| process_error(handle) end
383
+ ready_outputs.each do |handle| process_output(handle) end
384
+ ready_inputs.each do |handle| process_input(handle) end
385
+ end
386
+ end
387
+ end
388
+ end
389
+
390
+ def process_input(handle)
391
+ @logger.debug "input ready #{handle.inspect}"
392
+ handle.write(input_buffer.string)
393
+ @logger.debug format_output_for_log(input_buffer.string)
394
+ @logger.debug "wrote #{input_buffer.string.size} bytes"
395
+ input_buffer.string = ""
396
+ end
397
+
398
+ def process_output(handle)
399
+ @logger.debug "output ready #{handle.inspect}"
400
+ data = handle.readpartial(1024)
401
+ output_buffer << data
402
+ @history << data
403
+ @logger.debug format_input_for_log(data)
404
+ @logger.debug "read #{data.size} bytes"
405
+ handle_triggers(:output)
406
+ flush_triggers!(OutputTrigger) if ended?
407
+ # flush_output_buffer! unless ended?
408
+ end
409
+
410
+ def collect_remaining_output
411
+ if @output.nil?
412
+ @logger.debug "unable to collect output for missing output handle"
413
+ return
414
+ end
415
+ @logger.debug "collecting remaining output"
416
+ while data = @output.read_nonblock(1024)
417
+ output_buffer << data
418
+ @logger.debug "read #{data.size} bytes"
419
+ end
420
+ rescue EOFError, Errno::EIO => error
421
+ @logger.debug error.message
422
+ end
423
+
424
+ def wait_for_child_to_die
425
+ # Soon we should get a PTY::ChildExited
426
+ while running? || ended?
427
+ @logger.debug "waiting for child #{@pid} to die"
428
+ sleep 0.1
429
+ end
430
+ end
431
+
432
+ def process_error(handle)
433
+ @logger.debug "error on #{handle.inspect}"
434
+ raise NotImplementedError, "process_error()"
435
+ end
436
+
437
+ def process_timeout
438
+ @logger.debug "timeout"
439
+ handle_triggers(:timeout)
440
+ process_interruption(:timeout)
441
+ end
442
+
443
+ def handle_exit(status=status_from_waitpid)
444
+ return false if exited?
445
+ @logger.debug "handling exit of process #{@pid}"
446
+ @state = :exited
447
+ @status = status
448
+ handle_triggers(:exit)
449
+ if status == 0
450
+ process_interruption(:exit)
451
+ else
452
+ process_interruption(:abnormal_exit)
453
+ end
454
+ end
455
+
456
+ def status_from_waitpid
457
+ @logger.debug "waiting for exist status of #{@pid}"
458
+ ::Process.waitpid2(@pid)[1]
459
+ end
460
+
461
+ def handle_triggers(event)
462
+ klass = trigger_class_for_event(event)
463
+ matches = 0
464
+ triggers.grep(klass).each do |t|
465
+ @logger.debug "checking #{event} against #{t}"
466
+ if t.call(self) # match
467
+ matches += 1
468
+ @logger.debug "match trigger #{t}"
469
+ if blocker.equal?(t)
470
+ unblock!
471
+ end
472
+ if t.time_to_live
473
+ if t.time_to_live > 1
474
+ t.time_to_live -= 1
475
+ @logger.debug "trigger ttl reduced to #{t.time_to_live}"
476
+ else
477
+ triggers.delete(t)
478
+ @logger.debug "trigger removed"
479
+ end
480
+ end
481
+ break if t.exclusive?
482
+ else
483
+ @logger.debug "no match"
484
+ end
485
+ end
486
+ matches > 0
487
+ end
488
+
489
+ def handle_end_marker
490
+ return false if ended?
491
+ @logger.debug "end marker found"
492
+ output_buffer.string.gsub!(/#{END_MARKER}\s*/, '')
493
+ output_buffer.unscan
494
+ @state = :ended
495
+ @logger.debug "end marker expunged from output buffer"
496
+ @logger.debug "acknowledging end marker"
497
+ self.puts
498
+ end
499
+
500
+ def unblock!
501
+ @logger.debug "unblocked"
502
+ triggers.delete(@blocker)
503
+ @blocker = nil
504
+ end
505
+
506
+ def handle_child_exit
507
+ handle_eio do
508
+ yield
509
+ end
510
+ rescue PTY::ChildExited => error
511
+ @logger.debug "caught PTY::ChildExited"
512
+ collect_remaining_output
513
+ handle_exit(error.status)
514
+ end
515
+
516
+ def handle_eio
517
+ yield
518
+ rescue Errno::EIO => error
519
+ @logger.debug "Errno::EIO caught"
520
+ wait_for_child_to_die
521
+ end
522
+
523
+ def flush_triggers!(kind)
524
+ @logger.debug "flushing triggers matching #{kind}"
525
+ triggers.delete_if{|t| kind === t}
526
+ end
527
+
528
+ def merge_environment(new_env)
529
+ old_env = new_env.inject({}) do |old, (key, value)|
530
+ old[key] = ENV[key]
531
+ ENV[key] = value
532
+ old
533
+ end
534
+ yield
535
+ ensure
536
+ old_env.each_pair do |key, value|
537
+ if value.nil? then ENV.delete(key) else ENV[key] = value end
538
+ end
539
+ end
540
+
541
+ def process_interruption(reason)
542
+ if blocked?
543
+ self.interruption = reason
544
+ unless handle_triggers(:unsatisfied)
545
+ raise SystemError,
546
+ "Interrupted (#{reason}) while waiting for #{blocker}.\n" \
547
+ "Recent activity:\n" +
548
+ @history.buffer
549
+ end
550
+ unblock!
551
+ end
552
+ end
553
+
554
+ def format_output_for_log(text)
555
+ "\n" + text.split("\n").map{|l| ">> #{l}"}.join("\n")
556
+ end
557
+
558
+ def format_input_for_log(text)
559
+ "\n" + text.split("\n").map{|l| "<< #{l}"}.join("\n")
560
+ end
561
+
562
+ end
563
+ end
564
+
565
+ Greenletters.require_all_libs_relative_to(__FILE__)
566
+
data/script/console ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift(File.expand_path("../lib", File.dirname(__FILE__)))
3
+ require 'greenletters'
4
+ require 'irb'
5
+ IRB.start
@@ -0,0 +1,6 @@
1
+
2
+ require File.join(File.dirname(__FILE__), %w[spec_helper])
3
+
4
+ describe Greenletters do
5
+ end
6
+
@@ -0,0 +1,15 @@
1
+
2
+ require File.expand_path(
3
+ File.join(File.dirname(__FILE__), %w[.. lib greenletters]))
4
+
5
+ Spec::Runner.configure do |config|
6
+ # == Mock Framework
7
+ #
8
+ # RSpec uses it's own mocking framework by default. If you prefer to
9
+ # use mocha, flexmock or RR, uncomment the appropriate line:
10
+ #
11
+ # config.mock_with :mocha
12
+ # config.mock_with :flexmock
13
+ # config.mock_with :rr
14
+ end
15
+
File without changes
data/version.txt ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: greenletters
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: ruby
11
+ authors:
12
+ - Avdi Grimm
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-07-19 00:00:00 -04:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: bones
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 3
29
+ - 4
30
+ - 7
31
+ version: 3.4.7
32
+ type: :development
33
+ version_requirements: *id001
34
+ description: " Greenletterrs is a console automation framework, similar to the classic\n utility Expect. You give it a command to execute, and tell it which outputs\n or events to expect and how to respond to them.\n\n Greenletters also includes a set of Cucumber steps which simplify the task\n of spcifying interactive command-line applications.\n"
35
+ email: avdi@avdi.org
36
+ executables:
37
+ - greenletters
38
+ extensions: []
39
+
40
+ extra_rdoc_files:
41
+ - History.txt
42
+ - bin/greenletters
43
+ - version.txt
44
+ files:
45
+ - .gitignore
46
+ - History.txt
47
+ - README.org
48
+ - Rakefile
49
+ - bin/greenletters
50
+ - examples/adventure.rb
51
+ - examples/cucumber/adventure.feature
52
+ - examples/cucumber/greenletters.log
53
+ - examples/cucumber/support/env.rb
54
+ - lib/greenletters.rb
55
+ - lib/greenletters/cucumber_steps.rb
56
+ - script/console
57
+ - spec/greenletters_spec.rb
58
+ - spec/spec_helper.rb
59
+ - test/test_greenletters.rb
60
+ - version.txt
61
+ has_rdoc: true
62
+ homepage: http://github.com/avdi/greenletters
63
+ licenses: []
64
+
65
+ post_install_message:
66
+ rdoc_options:
67
+ - --main
68
+ - README.org
69
+ require_paths:
70
+ - lib
71
+ required_ruby_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ segments:
76
+ - 0
77
+ version: "0"
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ segments:
83
+ - 0
84
+ version: "0"
85
+ requirements: []
86
+
87
+ rubyforge_project: greenletters
88
+ rubygems_version: 1.3.6
89
+ signing_key:
90
+ specification_version: 3
91
+ summary: A Ruby console automation framework a la Expect
92
+ test_files:
93
+ - test/test_greenletters.rb