smart_message 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/.envrc +3 -0
  3. data/.gitignore +8 -0
  4. data/.travis.yml +7 -0
  5. data/CHANGELOG.md +100 -0
  6. data/COMMITS.md +196 -0
  7. data/Gemfile +6 -0
  8. data/Gemfile.lock +71 -0
  9. data/README.md +303 -0
  10. data/Rakefile +10 -0
  11. data/bin/console +14 -0
  12. data/bin/setup +8 -0
  13. data/docs/README.md +52 -0
  14. data/docs/architecture.md +370 -0
  15. data/docs/dispatcher.md +593 -0
  16. data/docs/examples.md +808 -0
  17. data/docs/getting-started.md +235 -0
  18. data/docs/ideas_to_think_about.md +329 -0
  19. data/docs/serializers.md +575 -0
  20. data/docs/transports.md +501 -0
  21. data/docs/troubleshooting.md +582 -0
  22. data/examples/01_point_to_point_orders.rb +200 -0
  23. data/examples/02_publish_subscribe_events.rb +364 -0
  24. data/examples/03_many_to_many_chat.rb +608 -0
  25. data/examples/README.md +335 -0
  26. data/examples/tmux_chat/README.md +283 -0
  27. data/examples/tmux_chat/bot_agent.rb +272 -0
  28. data/examples/tmux_chat/human_agent.rb +197 -0
  29. data/examples/tmux_chat/room_monitor.rb +158 -0
  30. data/examples/tmux_chat/shared_chat_system.rb +295 -0
  31. data/examples/tmux_chat/start_chat_demo.sh +190 -0
  32. data/examples/tmux_chat/stop_chat_demo.sh +22 -0
  33. data/lib/simple_stats.rb +57 -0
  34. data/lib/smart_message/base.rb +284 -0
  35. data/lib/smart_message/dispatcher/.keep +0 -0
  36. data/lib/smart_message/dispatcher.rb +146 -0
  37. data/lib/smart_message/errors.rb +29 -0
  38. data/lib/smart_message/header.rb +20 -0
  39. data/lib/smart_message/logger/base.rb +8 -0
  40. data/lib/smart_message/logger.rb +7 -0
  41. data/lib/smart_message/serializer/base.rb +23 -0
  42. data/lib/smart_message/serializer/json.rb +22 -0
  43. data/lib/smart_message/serializer.rb +10 -0
  44. data/lib/smart_message/transport/base.rb +85 -0
  45. data/lib/smart_message/transport/memory_transport.rb +69 -0
  46. data/lib/smart_message/transport/registry.rb +59 -0
  47. data/lib/smart_message/transport/stdout_transport.rb +62 -0
  48. data/lib/smart_message/transport.rb +41 -0
  49. data/lib/smart_message/version.rb +7 -0
  50. data/lib/smart_message/wrapper.rb +43 -0
  51. data/lib/smart_message.rb +54 -0
  52. data/smart_message.gemspec +53 -0
  53. metadata +252 -0
@@ -0,0 +1,295 @@
1
+ #!/usr/bin/env ruby
2
+ # examples/tmux_chat/shared_chat_system.rb
3
+ #
4
+ # Shared messaging system for the tmux chat visualization
5
+ # This provides the core message types and file-based transport for inter-pane communication
6
+
7
+ require_relative '../../lib/smart_message'
8
+ require 'fileutils'
9
+
10
+ # Create shared message queues directory
11
+ SHARED_DIR = '/tmp/smart_message_chat'
12
+ FileUtils.mkdir_p(SHARED_DIR)
13
+
14
+ # File-based transport for tmux communication
15
+ class FileTransport < SmartMessage::Transport::Base
16
+ def default_options
17
+ {
18
+ queue_dir: SHARED_DIR,
19
+ poll_interval: 0.1
20
+ }
21
+ end
22
+
23
+ def configure
24
+ @queue_dir = @options[:queue_dir]
25
+ @poll_interval = @options[:poll_interval]
26
+ @running = false
27
+ @subscriber_thread = nil
28
+
29
+ FileUtils.mkdir_p(@queue_dir)
30
+ end
31
+
32
+ def publish(message_header, message_payload)
33
+ message_data = {
34
+ header: message_header.to_h,
35
+ payload: message_payload,
36
+ timestamp: Time.now.to_f
37
+ }
38
+
39
+ # Write to room-specific queue file
40
+ room_id = extract_room_id(message_payload)
41
+ queue_file = File.join(@queue_dir, "#{room_id}.queue")
42
+
43
+ File.open(queue_file, 'a') do |f|
44
+ f.puts(JSON.generate(message_data))
45
+ f.flush
46
+ end
47
+ end
48
+
49
+ def start_subscriber
50
+ return if @running
51
+
52
+ @running = true
53
+ @subscriber_thread = Thread.new do
54
+ processed_positions = {}
55
+
56
+ while @running
57
+ Dir.glob(File.join(@queue_dir, "*.queue")).each do |queue_file|
58
+ room_id = File.basename(queue_file, '.queue')
59
+
60
+ if File.exist?(queue_file)
61
+ lines = File.readlines(queue_file)
62
+ start_pos = processed_positions[room_id] || 0
63
+
64
+ lines[start_pos..-1].each do |line|
65
+ begin
66
+ message_data = JSON.parse(line.strip)
67
+ header = SmartMessage::Header.new(message_data['header'])
68
+ receive(header, message_data['payload'])
69
+ rescue JSON::ParserError
70
+ # Skip malformed lines
71
+ end
72
+ end
73
+
74
+ processed_positions[room_id] = lines.length
75
+ end
76
+ end
77
+
78
+ sleep(@poll_interval)
79
+ end
80
+ end
81
+ end
82
+
83
+ def stop_subscriber
84
+ @running = false
85
+ @subscriber_thread&.join
86
+ end
87
+
88
+ private
89
+
90
+ def extract_room_id(payload)
91
+ begin
92
+ data = JSON.parse(payload)
93
+ data['room_id'] || 'global'
94
+ rescue
95
+ 'global'
96
+ end
97
+ end
98
+ end
99
+
100
+ # Register the file transport
101
+ SmartMessage::Transport.register(:file, FileTransport)
102
+
103
+ # Define the Chat Message
104
+ class ChatMessage < SmartMessage::Base
105
+ property :message_id
106
+ property :room_id
107
+ property :sender_id
108
+ property :sender_name
109
+ property :content
110
+ property :message_type # 'user', 'bot', 'system'
111
+ property :timestamp
112
+ property :mentions
113
+ property :metadata
114
+
115
+ config do
116
+ transport SmartMessage::Transport.create(:file)
117
+ serializer SmartMessage::Serializer::JSON.new
118
+ end
119
+
120
+ def self.process(message_header, message_payload)
121
+ # Default processing - agents will override this
122
+ end
123
+ end
124
+
125
+ # Define Bot Command Message
126
+ class BotCommandMessage < SmartMessage::Base
127
+ property :command_id
128
+ property :room_id
129
+ property :user_id
130
+ property :user_name
131
+ property :command
132
+ property :parameters
133
+ property :timestamp
134
+
135
+ config do
136
+ transport SmartMessage::Transport.create(:file)
137
+ serializer SmartMessage::Serializer::JSON.new
138
+ end
139
+
140
+ def self.process(message_header, message_payload)
141
+ # Default processing - bots will override this
142
+ end
143
+ end
144
+
145
+ # Define System Notification Message
146
+ class SystemNotificationMessage < SmartMessage::Base
147
+ property :notification_id
148
+ property :room_id
149
+ property :notification_type
150
+ property :content
151
+ property :timestamp
152
+ property :metadata
153
+
154
+ config do
155
+ transport SmartMessage::Transport.create(:file)
156
+ serializer SmartMessage::Serializer::JSON.new
157
+ end
158
+
159
+ def self.process(message_header, message_payload)
160
+ # Default processing
161
+ end
162
+ end
163
+
164
+ # Base Agent class with display utilities
165
+ class BaseAgent
166
+ attr_reader :agent_id, :name, :active_rooms
167
+
168
+ def initialize(agent_id:, name:, agent_type: 'agent')
169
+ @agent_id = agent_id
170
+ @name = name
171
+ @agent_type = agent_type
172
+ @active_rooms = []
173
+ @display_buffer = []
174
+ @transport = SmartMessage::Transport.create(:file)
175
+
176
+ setup_display
177
+ setup_subscriptions
178
+ start_message_processing
179
+ end
180
+
181
+ def join_room(room_id)
182
+ return if @active_rooms.include?(room_id)
183
+
184
+ @active_rooms << room_id
185
+ log_display("📥 Joined room: #{room_id}")
186
+
187
+ send_system_notification(
188
+ room_id: room_id,
189
+ notification_type: 'user_joined',
190
+ content: "#{@name} joined the room"
191
+ )
192
+ end
193
+
194
+ def leave_room(room_id)
195
+ return unless @active_rooms.include?(room_id)
196
+
197
+ @active_rooms.delete(room_id)
198
+ log_display("📤 Left room: #{room_id}")
199
+
200
+ send_system_notification(
201
+ room_id: room_id,
202
+ notification_type: 'user_left',
203
+ content: "#{@name} left the room"
204
+ )
205
+ end
206
+
207
+ def send_message(room_id:, content:, message_type: 'user')
208
+ return unless @active_rooms.include?(room_id)
209
+
210
+ message = ChatMessage.new(
211
+ message_id: generate_message_id,
212
+ room_id: room_id,
213
+ sender_id: @agent_id,
214
+ sender_name: @name,
215
+ content: content,
216
+ message_type: message_type,
217
+ timestamp: Time.now.iso8601,
218
+ mentions: extract_mentions(content),
219
+ metadata: { agent_type: @agent_type }
220
+ )
221
+
222
+ log_display("💬 [#{room_id}] #{@name}: #{content}")
223
+ message.publish
224
+ end
225
+
226
+ def shutdown
227
+ @active_rooms.dup.each { |room_id| leave_room(room_id) }
228
+ @transport.stop_subscriber
229
+ log_display("🔴 #{@name} shutdown")
230
+ end
231
+
232
+ protected
233
+
234
+ def setup_display
235
+ puts "\033[2J\033[H" # Clear screen
236
+ puts "┌─" + "─" * 50 + "┐"
237
+ puts "│ #{@agent_type.upcase}: #{@name.center(44)} │"
238
+ puts "├─" + "─" * 50 + "┤"
239
+ puts "│ Rooms: #{@active_rooms.join(', ').ljust(42)} │"
240
+ puts "├─" + "─" * 50 + "┤"
241
+ puts "│ Messages:".ljust(51) + " │"
242
+ puts "└─" + "─" * 50 + "┘"
243
+ puts
244
+ end
245
+
246
+ def log_display(message)
247
+ timestamp = Time.now.strftime("%H:%M:%S")
248
+ puts "[#{timestamp}] #{message}"
249
+ end
250
+
251
+ def setup_subscriptions
252
+ # Override in subclasses
253
+ end
254
+
255
+ def start_message_processing
256
+ @transport.start_subscriber
257
+ end
258
+
259
+ def send_system_notification(room_id:, notification_type:, content:)
260
+ notification = SystemNotificationMessage.new(
261
+ notification_id: "NOTIF-#{Time.now.to_i}-#{rand(1000)}",
262
+ room_id: room_id,
263
+ notification_type: notification_type,
264
+ content: content,
265
+ timestamp: Time.now.iso8601,
266
+ metadata: { triggered_by: @agent_id }
267
+ )
268
+
269
+ notification.publish
270
+ end
271
+
272
+ def generate_message_id
273
+ "MSG-#{@agent_id}-#{Time.now.to_i}-#{rand(1000)}"
274
+ end
275
+
276
+ def extract_mentions(content)
277
+ content.scan(/@(\w+)/).flatten
278
+ end
279
+ end
280
+
281
+ # Cleanup utility
282
+ def cleanup_shared_queues
283
+ FileUtils.rm_rf(SHARED_DIR) if Dir.exist?(SHARED_DIR)
284
+ end
285
+
286
+ # Signal handling for cleanup
287
+ trap('INT') do
288
+ cleanup_shared_queues
289
+ exit
290
+ end
291
+
292
+ trap('TERM') do
293
+ cleanup_shared_queues
294
+ exit
295
+ end
@@ -0,0 +1,190 @@
1
+ #!/bin/bash
2
+ # examples/tmux_chat/start_chat_demo.sh
3
+ #
4
+ # Tmux session manager for the many-to-many chat visualization
5
+
6
+ set -e
7
+
8
+ SESSION_NAME="smart_message_chat"
9
+ CHAT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10
+
11
+ # Colors for output
12
+ RED='\033[0;31m'
13
+ GREEN='\033[0;32m'
14
+ YELLOW='\033[1;33m'
15
+ BLUE='\033[0;34m'
16
+ NC='\033[0m' # No Color
17
+
18
+ echo -e "${BLUE}🚀 Starting SmartMessage Tmux Chat Demo${NC}"
19
+ echo -e "${BLUE}======================================${NC}"
20
+
21
+ # Check if tmux is installed
22
+ if ! command -v tmux &> /dev/null; then
23
+ echo -e "${RED}❌ tmux is not installed. Please install tmux first.${NC}"
24
+ echo "On macOS: brew install tmux"
25
+ echo "On Ubuntu: sudo apt-get install tmux"
26
+ exit 1
27
+ fi
28
+
29
+ # Check if Ruby is available
30
+ if ! command -v ruby &> /dev/null; then
31
+ echo -e "${RED}❌ Ruby is not installed.${NC}"
32
+ exit 1
33
+ fi
34
+
35
+ # Clean up any existing session
36
+ if tmux has-session -t $SESSION_NAME 2>/dev/null; then
37
+ echo -e "${YELLOW}🧹 Cleaning up existing session...${NC}"
38
+ tmux kill-session -t $SESSION_NAME
39
+ fi
40
+
41
+ # Clean up shared message queues
42
+ echo -e "${YELLOW}🧹 Cleaning up message queues...${NC}"
43
+ rm -rf /tmp/smart_message_chat
44
+
45
+ echo -e "${GREEN}📺 Creating tmux session layout...${NC}"
46
+
47
+ # Create new session with first window
48
+ tmux new-session -d -s $SESSION_NAME -x 120 -y 40
49
+
50
+ # Rename first window and set up control center
51
+ tmux rename-window -t $SESSION_NAME:0 "Chat-Control"
52
+
53
+ # Window 0: Control Center (2x2 layout)
54
+ echo -e "${GREEN}🏢 Setting up Control Center...${NC}"
55
+
56
+ # Top left: General room monitor
57
+ tmux send-keys -t $SESSION_NAME:0 "cd '$CHAT_DIR'" C-m
58
+ tmux send-keys -t $SESSION_NAME:0 "ruby room_monitor.rb general" C-m
59
+
60
+ # Split horizontally for top right: Tech room monitor
61
+ tmux split-window -t $SESSION_NAME:0 -h
62
+ tmux send-keys -t $SESSION_NAME:0.1 "cd '$CHAT_DIR'" C-m
63
+ tmux send-keys -t $SESSION_NAME:0.1 "ruby room_monitor.rb tech" C-m
64
+
65
+ # Split vertically (bottom left): Random room monitor
66
+ tmux split-window -t $SESSION_NAME:0.0 -v
67
+ tmux send-keys -t $SESSION_NAME:0.2 "cd '$CHAT_DIR'" C-m
68
+ tmux send-keys -t $SESSION_NAME:0.2 "ruby room_monitor.rb random" C-m
69
+
70
+ # Split vertically (bottom right): System overview
71
+ tmux split-window -t $SESSION_NAME:0.1 -v
72
+ tmux send-keys -t $SESSION_NAME:0.3 "cd '$CHAT_DIR'" C-m
73
+ tmux send-keys -t $SESSION_NAME:0.3 "echo 'SmartMessage Chat System'; echo '========================'; echo 'Rooms: general, tech, random'; echo 'Agents starting up...'; echo ''; echo 'Instructions:'; echo '1. Switch to other windows to see agents'; echo '2. In agent windows, type messages or commands'; echo '3. Use /join <room> to join rooms'; echo '4. Use /help for more commands'; tail -f /dev/null" C-m
74
+
75
+ # Wait a moment for room monitors to start
76
+ sleep 2
77
+
78
+ # Window 1: Human Agents (3 panes)
79
+ echo -e "${GREEN}👥 Setting up Human Agents...${NC}"
80
+ tmux new-window -t $SESSION_NAME -n "Human-Agents"
81
+
82
+ # Alice (left pane)
83
+ tmux send-keys -t $SESSION_NAME:1 "cd '$CHAT_DIR'" C-m
84
+ tmux send-keys -t $SESSION_NAME:1 "ruby human_agent.rb alice Alice" C-m
85
+
86
+ # Split for Bob (top right)
87
+ tmux split-window -t $SESSION_NAME:1 -h
88
+ tmux send-keys -t $SESSION_NAME:1.1 "cd '$CHAT_DIR'" C-m
89
+ tmux send-keys -t $SESSION_NAME:1.1 "ruby human_agent.rb bob Bob" C-m
90
+
91
+ # Split for Carol (bottom right)
92
+ tmux split-window -t $SESSION_NAME:1.1 -v
93
+ tmux send-keys -t $SESSION_NAME:1.2 "cd '$CHAT_DIR'" C-m
94
+ tmux send-keys -t $SESSION_NAME:1.2 "ruby human_agent.rb carol Carol" C-m
95
+
96
+ # Wait for agents to start
97
+ sleep 2
98
+
99
+ # Window 2: Bot Agents (2 panes)
100
+ echo -e "${GREEN}🤖 Setting up Bot Agents...${NC}"
101
+ tmux new-window -t $SESSION_NAME -n "Bot-Agents"
102
+
103
+ # HelpBot (left pane)
104
+ tmux send-keys -t $SESSION_NAME:2 "cd '$CHAT_DIR'" C-m
105
+ tmux send-keys -t $SESSION_NAME:2 "ruby bot_agent.rb helpbot HelpBot help,stats,time" C-m
106
+
107
+ # Split for FunBot (right pane)
108
+ tmux split-window -t $SESSION_NAME:2 -h
109
+ tmux send-keys -t $SESSION_NAME:2.1 "cd '$CHAT_DIR'" C-m
110
+ tmux send-keys -t $SESSION_NAME:2.1 "ruby bot_agent.rb funbot FunBot joke,weather,echo" C-m
111
+
112
+ # Wait for bots to start
113
+ sleep 2
114
+
115
+ # Auto-join agents to rooms for demo
116
+ echo -e "${GREEN}🏠 Auto-joining agents to rooms...${NC}"
117
+
118
+ # Alice joins general and tech
119
+ tmux send-keys -t $SESSION_NAME:1.0 "/join general" C-m
120
+ sleep 0.5
121
+ tmux send-keys -t $SESSION_NAME:1.0 "/join tech" C-m
122
+
123
+ # Bob joins general and random
124
+ tmux send-keys -t $SESSION_NAME:1.1 "/join general" C-m
125
+ sleep 0.5
126
+ tmux send-keys -t $SESSION_NAME:1.1 "/join random" C-m
127
+
128
+ # Carol joins tech and random
129
+ tmux send-keys -t $SESSION_NAME:1.2 "/join tech" C-m
130
+ sleep 0.5
131
+ tmux send-keys -t $SESSION_NAME:1.2 "/join random" C-m
132
+
133
+ # Bots join rooms
134
+ tmux send-keys -t $SESSION_NAME:2.0 "/join general" C-m
135
+ sleep 0.5
136
+ tmux send-keys -t $SESSION_NAME:2.0 "/join tech" C-m
137
+
138
+ tmux send-keys -t $SESSION_NAME:2.1 "/join general" C-m
139
+ sleep 0.5
140
+ tmux send-keys -t $SESSION_NAME:2.1 "/join random" C-m
141
+
142
+ sleep 1
143
+
144
+ # Send some initial messages to demonstrate the system
145
+ echo -e "${GREEN}💬 Sending demo messages...${NC}"
146
+
147
+ tmux send-keys -t $SESSION_NAME:1.0 "Hello everyone! I'm Alice." C-m
148
+ sleep 1
149
+ tmux send-keys -t $SESSION_NAME:1.1 "Hi Alice! Bob here." C-m
150
+ sleep 1
151
+ tmux send-keys -t $SESSION_NAME:1.2 "Carol joining the conversation!" C-m
152
+ sleep 1
153
+ tmux send-keys -t $SESSION_NAME:1.0 "/help" C-m
154
+ sleep 2
155
+ tmux send-keys -t $SESSION_NAME:1.1 "/joke" C-m
156
+ sleep 2
157
+
158
+ # Set focus to Human Agents window
159
+ tmux select-window -t $SESSION_NAME:1
160
+
161
+ echo -e "${GREEN}✅ Chat demo is ready!${NC}"
162
+ echo ""
163
+ echo -e "${BLUE}Navigation:${NC}"
164
+ echo "• Ctrl+b then 0: Control Center (room monitors)"
165
+ echo "• Ctrl+b then 1: Human Agents (Alice, Bob, Carol)"
166
+ echo "• Ctrl+b then 2: Bot Agents (HelpBot, FunBot)"
167
+ echo "• Ctrl+b then o: Cycle through panes"
168
+ echo "• Ctrl+b then arrow keys: Navigate panes"
169
+ echo ""
170
+ echo -e "${BLUE}Commands in agent panes:${NC}"
171
+ echo "• /join <room>: Join a room"
172
+ echo "• /leave <room>: Leave a room"
173
+ echo "• /list: List your active rooms"
174
+ echo "• /help: Show available commands"
175
+ echo "• /quit: Exit the agent"
176
+ echo ""
177
+ echo -e "${BLUE}Bot commands:${NC}"
178
+ echo "• /help: Show bot capabilities"
179
+ echo "• /joke: Get a random joke"
180
+ echo "• /weather <location>: Get weather"
181
+ echo "• /stats: Show bot statistics"
182
+ echo "• /time: Show current time"
183
+ echo "• /echo <message>: Echo your message"
184
+ echo ""
185
+ echo -e "${YELLOW}💡 Tip: Type messages directly to chat in your active rooms!${NC}"
186
+ echo ""
187
+ echo -e "${GREEN}🎭 Attaching to tmux session...${NC}"
188
+
189
+ # Attach to the session
190
+ tmux attach-session -t $SESSION_NAME
@@ -0,0 +1,22 @@
1
+ #!/bin/bash
2
+ # examples/tmux_chat/stop_chat_demo.sh
3
+ #
4
+ # Cleanup script for the tmux chat demo
5
+
6
+ SESSION_NAME="smart_message_chat"
7
+
8
+ echo "🧹 Stopping SmartMessage Tmux Chat Demo..."
9
+
10
+ # Kill the tmux session if it exists
11
+ if tmux has-session -t $SESSION_NAME 2>/dev/null; then
12
+ echo "🔴 Terminating tmux session..."
13
+ tmux kill-session -t $SESSION_NAME
14
+ else
15
+ echo "ℹ️ No active tmux session found."
16
+ fi
17
+
18
+ # Clean up shared message queues
19
+ echo "🗑️ Cleaning up message queues..."
20
+ rm -rf /tmp/smart_message_chat
21
+
22
+ echo "✅ Cleanup complete!"
@@ -0,0 +1,57 @@
1
+ # lib/simple_stats.rb
2
+
3
+ require 'ap'
4
+
5
+ # A dirt simple way of collecting statistics for use in testing
6
+ # that has a multi-level key structure
7
+ class SimpleStats
8
+ @@stat = Hash.new
9
+
10
+ class << self
11
+
12
+ # return the internal Hash
13
+ def stat
14
+ @@stat
15
+ end
16
+
17
+
18
+ # return the count for a specific multi-level key
19
+ def get(*args)
20
+ return 0 if args.empty?
21
+ key = get_key(args)
22
+ @@stat.key?(key) ? @@stat[key] : @@stat[key] = 0
23
+ end
24
+
25
+
26
+ # increment (add) counts to a multi-level key
27
+ def add(*args, how_many: 1)
28
+ return 0 if args.empty?
29
+ key = get_key(args)
30
+ @@stat.key?(key) ? @@stat[key] += how_many : @@stat[key] = how_many
31
+ end
32
+
33
+
34
+ def reset(*args)
35
+ if args.empty?
36
+ @@stat = {}
37
+ return 0
38
+ end
39
+ key = get_key(args)
40
+ @@stat[key] = 0
41
+ end
42
+
43
+
44
+ # simulate a multi-level key using a level seperater
45
+ def get_key(an_array, sep:'+')
46
+ an_array.join(sep)
47
+ end
48
+
49
+
50
+ # return a pretty printed representation of the statistics
51
+ def to_s
52
+ ap @@stat
53
+ end
54
+ end
55
+ end # class SimpleStat
56
+
57
+ SS = SimpleStats