gossip 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +9 -0
- data/LICENSE.txt +34 -0
- data/Manifest.txt +62 -0
- data/README.txt +6 -0
- data/Rakefile +137 -0
- data/examples/all-config-file-choices.yml +36 -0
- data/lib/gossip.rb +32 -0
- data/lib/gossip/command.rb +122 -0
- data/lib/gossip/cronies/campfire.rb +72 -0
- data/lib/gossip/cronies/jabber.rb +66 -0
- data/lib/gossip/cronies/smtp.rb +121 -0
- data/lib/gossip/cronies/stdout.rb +24 -0
- data/lib/gossip/cronies/trac.rb +82 -0
- data/lib/gossip/cronies/twitter.rb +50 -0
- data/lib/gossip/crony.rb +102 -0
- data/lib/gossip/multi-exceptions.rb +47 -0
- data/lib/gossip/preteen.rb +86 -0
- data/lib/gossip/site-config.rb +94 -0
- data/lib/gossip/social-universe.rb +18 -0
- data/lib/gossip/version.rb +8 -0
- data/pages/classes.html +58 -0
- data/pages/cronies.html +256 -0
- data/pages/css/LICENSE.txt +1 -0
- data/pages/css/Thumbs.db +0 -0
- data/pages/css/bg2.gif +0 -0
- data/pages/css/gossip5-header-flip.jpg +0 -0
- data/pages/css/left.gif +0 -0
- data/pages/css/left_on.gif +0 -0
- data/pages/css/main.css +242 -0
- data/pages/css/right.gif +0 -0
- data/pages/css/right_on.gif +0 -0
- data/pages/css/tvline.gif +0 -0
- data/pages/images/campfire.png +0 -0
- data/pages/images/classes.png +0 -0
- data/pages/images/deployment.png +0 -0
- data/pages/images/jabber-big.png +0 -0
- data/pages/images/jabber.png +0 -0
- data/pages/images/trac-bigger.png +0 -0
- data/pages/images/trac-detail.png +0 -0
- data/pages/images/twitter.png +0 -0
- data/pages/index.html +45 -0
- data/pages/installation.html +95 -0
- data/pages/scripts.html +166 -0
- data/pages/src/classes.graffle +0 -0
- data/pages/starting-to-use.html +200 -0
- data/pages/writing-new-scripts.html +38 -0
- data/scripts/fanout +64 -0
- data/scripts/svntell +71 -0
- data/scripts/watchdog +86 -0
- data/setup.rb +1585 -0
- data/test/script/fanout-slowtests.rb +40 -0
- data/test/script/svntell-slowtests.rb +40 -0
- data/test/script/util.rb +22 -0
- data/test/script/watchdog-slowtests.rb +56 -0
- data/test/unit/command-crony-interaction-tests.rb +116 -0
- data/test/unit/command-tests.rb +119 -0
- data/test/unit/crony-tests.rb +46 -0
- data/test/unit/multi-exception-tests.rb +70 -0
- data/test/unit/preteen-tests.rb +81 -0
- data/test/util/bff.rb +45 -0
- data/test/util/doghouse.rb +42 -0
- data/test/util/silly-little-test-program.rb +6 -0
- metadata +181 -0
data/History.txt
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
Version 0.3.0
|
2
|
+
* Version from _Everyday Scripting with Ruby_ renamed from Watchdog to
|
3
|
+
to Gossip and made into a Gem.
|
4
|
+
* Generalized so that watchdog is only one example of the more general
|
5
|
+
library.
|
6
|
+
* Can now spread gossip to Campfire. (Idea taken from snitch.rubyforge.org,
|
7
|
+
John Nunemaker.)
|
8
|
+
* Can now spread gossip to Trac.
|
9
|
+
* Can now spread gossip to Twitter (credit again to John Nunemaker).
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
This software is Copyright (C) 2007 by Brian Marick <marick@exampler.com>. It is licensed according to "the Ruby license". Specifically:
|
2
|
+
|
3
|
+
You can redistribute it and/or modify it under either the terms of the GNU General Public License, version 2, <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html> or the conditions below:
|
4
|
+
|
5
|
+
1. You may make and give away verbatim copies of the source form of the
|
6
|
+
software without restriction, provided that you duplicate all of the
|
7
|
+
original copyright notices and associated disclaimers.
|
8
|
+
|
9
|
+
2. You may modify your copy of the software in any way, provided that
|
10
|
+
you do at least ONE of the following:
|
11
|
+
|
12
|
+
a) place your modifications in the Public Domain or otherwise
|
13
|
+
make them Freely Available, such as by posting said
|
14
|
+
modifications to Usenet or an equivalent medium, or by allowing
|
15
|
+
the author to include your modifications in the software.
|
16
|
+
|
17
|
+
b) use the modified software only within your corporation or
|
18
|
+
organization.
|
19
|
+
|
20
|
+
c) make other distribution arrangements with the author.
|
21
|
+
|
22
|
+
3. You may modify and include part of the software into any other
|
23
|
+
software (possibly commercial).
|
24
|
+
|
25
|
+
4. Text or files supplied as input to or produced as output from
|
26
|
+
the software do not automatically fall under the copyright of the
|
27
|
+
software, but belong to whomever generated them, and may be sold
|
28
|
+
commercially, and may be aggregated with this software.
|
29
|
+
|
30
|
+
5. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
|
31
|
+
IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
|
32
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
33
|
+
PURPOSE.
|
34
|
+
|
data/Manifest.txt
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
History.txt
|
2
|
+
LICENSE.txt
|
3
|
+
Manifest.txt
|
4
|
+
README.txt
|
5
|
+
Rakefile
|
6
|
+
examples/all-config-file-choices.yml
|
7
|
+
lib/gossip.rb
|
8
|
+
lib/gossip/command.rb
|
9
|
+
lib/gossip/cronies/campfire.rb
|
10
|
+
lib/gossip/cronies/jabber.rb
|
11
|
+
lib/gossip/cronies/smtp.rb
|
12
|
+
lib/gossip/cronies/stdout.rb
|
13
|
+
lib/gossip/cronies/trac.rb
|
14
|
+
lib/gossip/cronies/twitter.rb
|
15
|
+
lib/gossip/crony.rb
|
16
|
+
lib/gossip/multi-exceptions.rb
|
17
|
+
lib/gossip/preteen.rb
|
18
|
+
lib/gossip/site-config.rb
|
19
|
+
lib/gossip/social-universe.rb
|
20
|
+
lib/gossip/version.rb
|
21
|
+
pages/classes.html
|
22
|
+
pages/cronies.html
|
23
|
+
pages/css/LICENSE.txt
|
24
|
+
pages/css/Thumbs.db
|
25
|
+
pages/css/bg2.gif
|
26
|
+
pages/css/gossip5-header-flip.jpg
|
27
|
+
pages/css/left.gif
|
28
|
+
pages/css/left_on.gif
|
29
|
+
pages/css/main.css
|
30
|
+
pages/css/right.gif
|
31
|
+
pages/css/right_on.gif
|
32
|
+
pages/css/tvline.gif
|
33
|
+
pages/images/campfire.png
|
34
|
+
pages/images/classes.png
|
35
|
+
pages/images/deployment.png
|
36
|
+
pages/images/jabber-big.png
|
37
|
+
pages/images/jabber.png
|
38
|
+
pages/images/trac-bigger.png
|
39
|
+
pages/images/trac-detail.png
|
40
|
+
pages/images/twitter.png
|
41
|
+
pages/index.html
|
42
|
+
pages/installation.html
|
43
|
+
pages/scripts.html
|
44
|
+
pages/src/classes.graffle
|
45
|
+
pages/starting-to-use.html
|
46
|
+
pages/writing-new-scripts.html
|
47
|
+
scripts/fanout
|
48
|
+
scripts/svntell
|
49
|
+
scripts/watchdog
|
50
|
+
setup.rb
|
51
|
+
test/script/fanout-slowtests.rb
|
52
|
+
test/script/svntell-slowtests.rb
|
53
|
+
test/script/util.rb
|
54
|
+
test/script/watchdog-slowtests.rb
|
55
|
+
test/unit/command-crony-interaction-tests.rb
|
56
|
+
test/unit/command-tests.rb
|
57
|
+
test/unit/crony-tests.rb
|
58
|
+
test/unit/multi-exception-tests.rb
|
59
|
+
test/unit/preteen-tests.rb
|
60
|
+
test/util/bff.rb
|
61
|
+
test/util/doghouse.rb
|
62
|
+
test/util/silly-little-test-program.rb
|
data/README.txt
ADDED
@@ -0,0 +1,6 @@
|
|
1
|
+
Gossip is a library that provides a consistent interface to "cronies" like Twitter, Campfire, email, and Jabber. It comes with scripts that use that library to do useful things. There is a {more detailed description}[http://gossip.rubyforge.org].
|
2
|
+
|
3
|
+
There are nicely-formatted {installation instructions}[http://gossip.rubyforge.org/installation.html].
|
4
|
+
|
5
|
+
There is also a {description of the main classes}[http://gossip.rubyforge.org/classes.html] to help you understand Gossip, plus instructions for {writing new scripts}[http://gossip.rubyforge.org/writing-new-scripts.html].
|
6
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Created by Brian Marick on 2007-07-03.
|
4
|
+
# Copyright (c) 2007. All rights reserved.
|
5
|
+
|
6
|
+
require 'hoe'
|
7
|
+
|
8
|
+
Dir.chdir('lib') do
|
9
|
+
# Hack: This prevents requires of the same file looking
|
10
|
+
# different in tests and causing annoying warnings.
|
11
|
+
require 'gossip/version'
|
12
|
+
end
|
13
|
+
|
14
|
+
PROJECT='gossip'
|
15
|
+
THIS_RELEASE=Gossip::Version
|
16
|
+
|
17
|
+
|
18
|
+
Hoe.new(PROJECT, THIS_RELEASE) do |p|
|
19
|
+
p.rubyforge_name = PROJECT
|
20
|
+
p.changes = "See History.txt"
|
21
|
+
p.author = "Brian Marick"
|
22
|
+
p.description = "Library to broadcast messages to multiple destinations + scripts that use it"
|
23
|
+
p.summary = p.description
|
24
|
+
p.email = "marick@exampler.com"
|
25
|
+
p.extra_deps = [['s4t-utils', '>= 1.0.3'],
|
26
|
+
['xmpp4r', '>= 0.3.1'],
|
27
|
+
['tinder', '>= 0.1.4'],
|
28
|
+
['snitch', '>= 0.1.1'],
|
29
|
+
['twitter', '>= 0.2.0'],
|
30
|
+
['user-choices', '>= 1.1.4'],
|
31
|
+
]
|
32
|
+
p.test_globs = "test/**/*tests.rb"
|
33
|
+
p.rdoc_pattern = %r{README.txt|History.txt|lib/gossip.rb|lib/gossip/.+\.rb}
|
34
|
+
p.url = "http://gossip.rubyforge.org"
|
35
|
+
p.remote_rdoc_dir = 'rdoc'
|
36
|
+
end
|
37
|
+
|
38
|
+
require 's4t-utils/rake-task-helpers'
|
39
|
+
require 's4t-utils/os'
|
40
|
+
|
41
|
+
def assert_in(dir, taskname)
|
42
|
+
unless Dir.pwd == dir
|
43
|
+
puts "Run task '#{taskname}' from directory '#{dir}'."
|
44
|
+
exit 1
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def confirmed_step(name, required_dir = nil)
|
49
|
+
assert_in(required_dir, name) if required_dir
|
50
|
+
STDOUT.puts "** #{name} **"
|
51
|
+
STDOUT.puts `rake #{name}`
|
52
|
+
STDOUT.print 'OK? > '
|
53
|
+
exit if STDIN.readline =~ /[nN]/
|
54
|
+
end
|
55
|
+
|
56
|
+
class HoeLike
|
57
|
+
|
58
|
+
def pull(key)
|
59
|
+
@keys[key] || raise("Missing key #{key.inspect}")
|
60
|
+
end
|
61
|
+
|
62
|
+
def initialize(keys)
|
63
|
+
@keys = keys
|
64
|
+
project = pull(:project)
|
65
|
+
this_release = pull(:this_release)
|
66
|
+
login = pull(:login)
|
67
|
+
web_site_root = pull(:web_site_root)
|
68
|
+
export_root = pull(:export_root)
|
69
|
+
|
70
|
+
root = "svn+ssh://#{login}/var/svn/#{project}"
|
71
|
+
project_exports = "#{export_root}/#{project}"
|
72
|
+
|
73
|
+
desc "Run fast tests."
|
74
|
+
task 'fast' do
|
75
|
+
S4tUtils.run_particular_tests('test', 'fast')
|
76
|
+
end
|
77
|
+
|
78
|
+
desc "Run slow tests."
|
79
|
+
task 'slow' do
|
80
|
+
S4tUtils.run_particular_tests('test', 'slow')
|
81
|
+
end
|
82
|
+
|
83
|
+
desc "Upload all the web pages"
|
84
|
+
task 'upload_pages' do | task |
|
85
|
+
exec = "scp -r #{web_site_root}/* #{login}:/var/www/gforge-projects/#{project}/"
|
86
|
+
puts exec
|
87
|
+
system(exec)
|
88
|
+
end
|
89
|
+
|
90
|
+
desc "Tag release with current version."
|
91
|
+
task 'tag_release' do
|
92
|
+
from = "#{root}/trunk"
|
93
|
+
to = "#{root}/tags/rel-#{this_release}"
|
94
|
+
message = "Release #{this_release}"
|
95
|
+
exec = "svn copy -m '#{message}' #{from} #{to}"
|
96
|
+
puts exec
|
97
|
+
system(exec)
|
98
|
+
end
|
99
|
+
|
100
|
+
desc "Export to #{project_exports}"
|
101
|
+
task 'export' do
|
102
|
+
Dir.chdir(export_root) do
|
103
|
+
rm_rf project
|
104
|
+
exec = "svn export #{root}/trunk #{project}"
|
105
|
+
puts exec
|
106
|
+
system exec
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
desc "Complete release of everything - asks for confirmation after steps"
|
112
|
+
# Because in Ruby 1.8.6, Rake doesn't notice subtask failures, so it
|
113
|
+
# won't stop for us.
|
114
|
+
task 'release_everything' do
|
115
|
+
confirmed_step 'check_manifest'
|
116
|
+
confirmed_step 'test'
|
117
|
+
confirmed_step 'export'
|
118
|
+
Dir.chdir(project_exports) do
|
119
|
+
puts "Working in #{Dir.pwd}"
|
120
|
+
confirmed_step 'upload_pages', project_exports
|
121
|
+
confirmed_step 'publish_docs', project_exports
|
122
|
+
ENV['VERSION'] = this_release
|
123
|
+
confirmed_step 'release', project_exports
|
124
|
+
end
|
125
|
+
confirmed_step 'tag_release'
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
HoeLike.new(:project => PROJECT, :this_release => THIS_RELEASE,
|
133
|
+
:login => "marick@rubyforge.org",
|
134
|
+
:web_site_root => 'pages',
|
135
|
+
:export_root => "#{S4tUtils.find_home}/tmp/exports")
|
136
|
+
|
137
|
+
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# Turn cronies (destinations) on or off.
|
2
|
+
|
3
|
+
standard-output: true
|
4
|
+
jabber: false
|
5
|
+
mail: false
|
6
|
+
campfire: false
|
7
|
+
trac: false
|
8
|
+
twitter: false
|
9
|
+
|
10
|
+
# Configurations for each destination.
|
11
|
+
# You can delete or ignore ones you don't plan to use.
|
12
|
+
|
13
|
+
jabber-account: jabber@example.com
|
14
|
+
jabber-password: not-the-real-one
|
15
|
+
jabber-to:
|
16
|
+
- brianmarick@example.com
|
17
|
+
|
18
|
+
mail-to:
|
19
|
+
- marick@example.com
|
20
|
+
- dawn@example.com
|
21
|
+
|
22
|
+
mail-from: bmarick@mac.com
|
23
|
+
mail-server: smtp.mac.com
|
24
|
+
mail-port: 587
|
25
|
+
mail-from-domain: localhost.localdomain
|
26
|
+
mail-account: bmarick
|
27
|
+
mail-password: not-the-real-one
|
28
|
+
mail-authentication: login
|
29
|
+
|
30
|
+
campfire-login: marick@example.com
|
31
|
+
campfire-password: not-the-real-one
|
32
|
+
campfire-subdomain: example
|
33
|
+
campfire-room: room
|
34
|
+
|
35
|
+
twitter-login: twittername
|
36
|
+
twitter-password: some password
|
data/lib/gossip.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
#! /opt/local/bin/ruby
|
2
|
+
#
|
3
|
+
# Created by Brian Marick on 2007-09-15.
|
4
|
+
# Copyright (c) 2007. All rights reserved.
|
5
|
+
|
6
|
+
require 's4t-utils'
|
7
|
+
require 'active_support' # because it includes its own copy of a gem
|
8
|
+
# that user-choices would otherwise include.
|
9
|
+
|
10
|
+
require 'gossip/social-universe'
|
11
|
+
require 'gossip/preteen'
|
12
|
+
require 'gossip/crony'
|
13
|
+
require 'gossip/command'
|
14
|
+
require 'gossip/multi-exceptions'
|
15
|
+
require 'gossip/version'
|
16
|
+
|
17
|
+
# Gossip is a library used to write scripts that collect useful information
|
18
|
+
# and send it to various places (like jabber, email, trac, etc.). The
|
19
|
+
# guiding metaphor is of a Preteen who has Cronies. A Crony is someone who
|
20
|
+
# might conceivably hear gossip from the Preteen. However, some cronies are
|
21
|
+
# Best Friends Forever, and others are (temporarily) not speaking to the
|
22
|
+
# Preteen. Only the former hear the gossip.
|
23
|
+
#
|
24
|
+
# There's also a whole bunch of people who are not Cronies. We won't speak
|
25
|
+
# of them. All we need know of them is that they can never ever receive
|
26
|
+
# gossip because they can never ever become a Best Friend Forever.
|
27
|
+
#
|
28
|
+
# Most of the code in the script will be in a subclass of GossipCommand.
|
29
|
+
# The execute method of that command gathers the information and passes
|
30
|
+
# it to the Preteen.
|
31
|
+
module Gossip
|
32
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
#! /opt/local/bin/ruby
|
2
|
+
#
|
3
|
+
# Created by Brian Marick on 2007-09-15.
|
4
|
+
# Copyright (c) 2007. All rights reserved.
|
5
|
+
|
6
|
+
require 'user-choices'
|
7
|
+
require 'pp'
|
8
|
+
require 'gossip/preteen'
|
9
|
+
|
10
|
+
module Gossip
|
11
|
+
|
12
|
+
|
13
|
+
# THe GossipCommand knows how to set up a Preteen's Crony's. A particular
|
14
|
+
# script should subclass it to add script-specific behavior. See the
|
15
|
+
# examples, particularly fanout.rb, for details. To understand more about
|
16
|
+
# GossipCommand, see UserChoices::Command in the user-choices gem.
|
17
|
+
# http://user-choices.rubyforge.org.
|
18
|
+
class GossipCommand < UserChoices::Command
|
19
|
+
include UserChoices
|
20
|
+
|
21
|
+
attr_accessor :preteen
|
22
|
+
def initialize(preteen)
|
23
|
+
self.preteen = preteen
|
24
|
+
super()
|
25
|
+
end
|
26
|
+
|
27
|
+
# Text describing the command-line arguments (not the options).
|
28
|
+
# Traditionally looks something like:
|
29
|
+
# "Usage: ruby #{$0} [options] args..."
|
30
|
+
# Can be a single line or array of lines.
|
31
|
+
# Must be overridden.
|
32
|
+
def usage; subclass_responsibility; end
|
33
|
+
|
34
|
+
# Override to name the configuration file specific to this script.
|
35
|
+
def script_config_file; subclass_responsibility; end
|
36
|
+
|
37
|
+
def add_choices(builder)
|
38
|
+
first_set_are_specific_to_script(builder)
|
39
|
+
all_crony_switches_come_next(builder)
|
40
|
+
crony_specific_switches_are_third(builder)
|
41
|
+
helpful_switches_go_last(builder)
|
42
|
+
|
43
|
+
# Can go anywhere - doesn't appear in help text.
|
44
|
+
add_arglist_choice(builder)
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
# By default, the command will stuff all arguments into
|
49
|
+
# a choice named :arglist.
|
50
|
+
def add_arglist_choice(builder)
|
51
|
+
builder.add_choice(:arglist) { | command_line |
|
52
|
+
command_line.uses_arglist
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
def postprocess_user_choices
|
57
|
+
if @user_choices[:choices]
|
58
|
+
puts "Looking for configuration information in:"
|
59
|
+
puts " " + File.join(S4tUtils.find_home, script_config_file)
|
60
|
+
puts " " + File.join(S4tUtils.find_home, gossip_config_file)
|
61
|
+
puts "Choices gathered from all sources:"
|
62
|
+
puts alphabetical_symbol_hash(@user_choices)
|
63
|
+
exit
|
64
|
+
end
|
65
|
+
|
66
|
+
preteen.cronies.each do | crony |
|
67
|
+
crony.user_choices = @user_choices
|
68
|
+
crony.postprocess_user_choices
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def describe_all_but_options
|
73
|
+
[usage,
|
74
|
+
"Site-wide defaults are noted below.",
|
75
|
+
"Override them in the '#{script_config_file}' or '#{gossip_config_file}' files in your home folder."
|
76
|
+
].flatten
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def first_set_are_specific_to_script(builder)
|
82
|
+
if self.respond_to?(:add_choices_specific_to_script)
|
83
|
+
builder.section_specific_to_script do
|
84
|
+
add_choices_specific_to_script(builder)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def all_crony_switches_come_next(builder)
|
90
|
+
builder.section("that determine who hears the gossip") do
|
91
|
+
preteen.cronies.each do | crony |
|
92
|
+
crony.add_bff_choice(builder)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def crony_specific_switches_are_third(builder)
|
98
|
+
builder.section("that apply to particular listeners") do
|
99
|
+
preteen.cronies.each do | crony |
|
100
|
+
crony.add_configuration_choices(builder)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def helpful_switches_go_last(builder)
|
106
|
+
builder.add_choice(:choices,
|
107
|
+
:default => false,
|
108
|
+
:type => :boolean) { | command_line |
|
109
|
+
command_line.uses_switch("--choices",
|
110
|
+
"Show all configuration choices.")
|
111
|
+
}
|
112
|
+
end
|
113
|
+
|
114
|
+
def alphabetical_symbol_hash(hash)
|
115
|
+
key_strings = hash.keys.collect { |k| k.to_s }.sort
|
116
|
+
keys = key_strings.collect { |s| s.to_sym }
|
117
|
+
keys.collect do |key|
|
118
|
+
" #{key.inspect}=>#{hash[key].inspect}"
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|