Skip to content

Commit 1b6fe73

Browse files
committed
Add a who verb.
1 parent f4fe149 commit 1b6fe73

6 files changed

Lines changed: 243 additions & 3 deletions

File tree

src/constants.moo

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@ define THING = #19;
1919
define BLOCK = #20;
2020
define TITLE = #21;
2121
define HTML = #22;
22-
define LIST = #24;
2322
define MATCH = #23;
23+
define LIST = #24;
24+
define TABLE = #25;
25+
define INT_PROTO = #26;
2426
define FAILED_MATCH = #-3;
2527
define AMBIGUOUS = #-2;
2628
define NOTHING = #-1;

src/int_proto.moo

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
object INT_PROTO
2+
name: "Integer Prototype"
3+
parent: ROOT
4+
location: FIRST_ROOM
5+
owner: HACKER
6+
readable: true
7+
8+
verb format_time_seconds (this none this) owner: HACKER flags: "rxd"
9+
"Convert integer seconds to human-readable time format";
10+
"Usage: seconds_value:format_time_seconds()";
11+
{seconds} = args;
12+
typeof(seconds) != INT && raise(E_TYPE, "Method must be called on integer");
13+
seconds < 0 && raise(E_INVARG, "Seconds cannot be negative");
14+
seconds < 60 && return tostr(seconds, "s");
15+
minutes = seconds / 60;
16+
remaining_seconds = seconds % 60;
17+
minutes < 60 && return tostr(minutes, "m", remaining_seconds, "s");
18+
hours = minutes / 60;
19+
remaining_minutes = minutes % 60;
20+
hours < 24 && return tostr(hours, "h", remaining_minutes, "m");
21+
days = hours / 24;
22+
remaining_hours = hours % 24;
23+
return tostr(days, "d", remaining_hours, "h");
24+
endverb
25+
26+
verb test_format_time_seconds_basic (this none this) owner: HACKER flags: "rxd"
27+
"Test basic time formatting for seconds, minutes, hours";
28+
"Test seconds only";
29+
30:format_time_seconds() != "30s" && return E_ASSERT;
30+
0:format_time_seconds() != "0s" && return E_ASSERT;
31+
59:format_time_seconds() != "59s" && return E_ASSERT;
32+
"Test minutes and seconds";
33+
60:format_time_seconds() != "1m0s" && return E_ASSERT;
34+
90:format_time_seconds() != "1m30s" && return E_ASSERT;
35+
3599:format_time_seconds() != "59m59s" && return E_ASSERT;
36+
"Test hours and minutes";
37+
3600:format_time_seconds() != "1h0m" && return E_ASSERT;
38+
7890:format_time_seconds() != "2h11m" && return E_ASSERT;
39+
return true;
40+
endverb
41+
42+
verb test_format_time_seconds_edge_cases (this none this) owner: HACKER flags: "rxd"
43+
"Test edge cases and error conditions";
44+
"Test days";
45+
86400:format_time_seconds() != "1d0h" && return E_ASSERT;
46+
90061:format_time_seconds() != "1d1h" && return E_ASSERT;
47+
"Test error conditions";
48+
try
49+
-1:format_time_seconds();
50+
return E_ASSERT;
51+
except e (E_INVARG)
52+
"Expected error for negative seconds";
53+
endtry
54+
return true;
55+
endverb
56+
57+
verb test_format_time_seconds_realistic (this none this) owner: HACKER flags: "rxd"
58+
"Test realistic idle/connection times";
59+
"Test typical idle times";
60+
180:format_time_seconds() != "3m0s" && return E_ASSERT;
61+
1800:format_time_seconds() != "30m0s" && return E_ASSERT;
62+
5400:format_time_seconds() != "1h30m" && return E_ASSERT;
63+
"Test long connection times";
64+
28800:format_time_seconds() != "8h0m" && return E_ASSERT;
65+
172800:format_time_seconds() != "2d0h" && return E_ASSERT;
66+
return true;
67+
endverb
68+
endobject

src/list.moo

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,8 @@ object LIST
7272
items = {"Apple", "Banana", "Cherry"};
7373
unordered = this:mk(items);
7474
ordered = this:mk(items, true);
75-
7675
plain_unordered = unordered:compose($nothing, 'text_plain, $nothing);
7776
plain_ordered = ordered:compose($nothing, 'text_plain, $nothing);
78-
7977
plain_unordered != "* Apple\n* Banana\n* Cherry" && return E_ASSERT;
8078
plain_ordered != "1. Apple\n1. Banana\n1. Cherry" && return E_ASSERT;
8179
return true;

src/player.moo

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,4 +106,33 @@ object PLAYER
106106
verb mk_connected_event (this none this) owner: HACKER flags: "rxd"
107107
return $event:mk_say(this, $sub:nc(), " ", $sub:self_alt("have", "has"), " disconnected.");
108108
endverb
109+
110+
verb "who @who" (any any any) owner: ARCH_WIZARD flags: "rxd"
111+
"Display list of connected players using table format";
112+
caller != player && return E_PERM;
113+
players = connected_players();
114+
!players && return this:tell($event:mk_not_found(this, "No players are currently connected."));
115+
"Build table data";
116+
headers = {"Name", "Idle", "Connected", "Location"};
117+
rows = {};
118+
for p in (players)
119+
if (typeof(idle_time = idle_seconds(p)) != ERR)
120+
name = p:name();
121+
idle_str = idle_time:format_time_seconds();
122+
conn_str = connected_seconds(p):format_time_seconds();
123+
location_name = valid(p.location) ? p.location:name() | "(nowhere)";
124+
rows = {@rows, {name, idle_str, conn_str, location_name}};
125+
endif
126+
endfor
127+
"Create and display the table";
128+
if (rows)
129+
table_obj = $table:mk(headers, rows);
130+
title_obj = $title:mk("Who's Online");
131+
content = $block:mk(title_obj, table_obj);
132+
event = $event:mk_who(player, content);
133+
this:tell(event);
134+
else
135+
this:tell($event:mk_not_found(this, "No connected players found."));
136+
endif
137+
endverb
109138
endobject

src/sysobj.moo

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ object SYSOBJ
1313
property first_room (owner: HACKER, flags: "r") = FIRST_ROOM;
1414
property hacker (owner: HACKER, flags: "r") = HACKER;
1515
property html (owner: HACKER, flags: "r") = HTML;
16+
property int_proto (owner: HACKER, flags: "r") = INT_PROTO;
1617
property list (owner: HACKER, flags: "r") = LIST;
1718
property list_proto (owner: HACKER, flags: "r") = LIST_PROTO;
1819
property login (owner: HACKER, flags: "r") = LOGIN;
@@ -27,6 +28,7 @@ object SYSOBJ
2728
property str_proto (owner: HACKER, flags: "r") = STR_PROTO;
2829
property sub (owner: HACKER, flags: "r") = SUB;
2930
property sysobj (owner: HACKER, flags: "r") = SYSOBJ;
31+
property table (owner: HACKER, flags: "r") = TABLE;
3032
property thing (owner: HACKER, flags: "r") = THING;
3133
property title (owner: HACKER, flags: "r") = TITLE;
3234
property wiz (owner: HACKER, flags: "r") = WIZ;

src/table.moo

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
object TABLE
2+
name: "Table Content Flyweight Delegate"
3+
parent: ROOT
4+
location: FIRST_ROOM
5+
owner: HACKER
6+
readable: true
7+
8+
verb mk (this none this) owner: HACKER flags: "rxd"
9+
"Create table flyweight with headers and rows";
10+
{headers, rows} = args;
11+
typeof(headers) != LIST && raise(E_TYPE, "Headers must be a list");
12+
typeof(rows) != LIST && raise(E_TYPE, "Rows must be a list");
13+
return <this, [headers -> headers, rows -> rows]>;
14+
endverb
15+
16+
verb compose (this none this) owner: HACKER flags: "rxd"
17+
"Compose table content into appropriate format";
18+
{render_for, content_type, event} = args;
19+
headers = this.headers;
20+
rows = this.rows;
21+
if (!headers || !rows)
22+
if (content_type == 'text_html)
23+
return <$html, {"p", {}, {"(empty table)"}}>;
24+
else
25+
return "(empty table)";
26+
endif
27+
endif
28+
if (content_type == 'text_html)
29+
"Build HTML table structure";
30+
table_children = {};
31+
"Add header row if present";
32+
if (headers)
33+
header_cells = {};
34+
for header in (headers)
35+
if (typeof(header) == STR)
36+
header_content = header;
37+
elseif (typeof(header) == FLYWEIGHT)
38+
header_content = header:compose(@args);
39+
else
40+
header_content = tostr(header);
41+
endif
42+
header_cells = {@header_cells, <$html, {"th", {}, {header_content}}>};
43+
endfor
44+
thead = <$html, {"thead", {}, {<$html, {"tr", {}, header_cells}>}}>;
45+
table_children = {@table_children, thead};
46+
endif
47+
"Add body rows";
48+
body_rows = {};
49+
for row in (rows)
50+
row_cells = {};
51+
for cell in (row)
52+
if (typeof(cell) == STR)
53+
cell_content = cell;
54+
elseif (typeof(cell) == FLYWEIGHT)
55+
cell_content = cell:compose(@args);
56+
else
57+
cell_content = tostr(cell);
58+
endif
59+
row_cells = {@row_cells, <$html, {"td", {}, {cell_content}}>};
60+
endfor
61+
body_rows = {@body_rows, <$html, {"tr", {}, row_cells}>};
62+
endfor
63+
tbody = <$html, {"tbody", {}, body_rows}>;
64+
table_children = {@table_children, tbody};
65+
return <$html, {"table", {}, table_children}>;
66+
else
67+
"Plain text table output";
68+
result = {};
69+
"Calculate column widths";
70+
widths = {};
71+
for i in [1..length(headers)]
72+
widths = {@widths, length(tostr(headers[i]))};
73+
endfor
74+
for row in (rows)
75+
for i in [1..min(length(row), length(widths))]
76+
cell_width = length(tostr(row[i]));
77+
if (cell_width > widths[i])
78+
widths[i] = cell_width;
79+
endif
80+
endfor
81+
endfor
82+
"Build header line";
83+
line = "";
84+
for i in [1..length(headers)]
85+
if (i > 1)
86+
line = line + " | ";
87+
endif
88+
line = line + $str_proto:pad_right(tostr(headers[i]), widths[i]);
89+
endfor
90+
result = {@result, line};
91+
"Build separator";
92+
line = "";
93+
for i in [1..length(headers)]
94+
if (i > 1)
95+
line = line + "-+-";
96+
endif
97+
line = line + $str_proto:space(widths[i], "-");
98+
endfor
99+
result = {@result, line};
100+
"Build data rows";
101+
for row in (rows)
102+
line = "";
103+
for i in [1..length(headers)]
104+
if (i > 1)
105+
line = line + " | ";
106+
endif
107+
cell = i <= length(row) ? tostr(row[i]) | "";
108+
line = line + $str_proto:pad_right(cell, widths[i]);
109+
endfor
110+
result = {@result, line};
111+
endfor
112+
return result:join("\n");
113+
endif
114+
endverb
115+
116+
verb test_simple_table (this none this) owner: HACKER flags: "rxd"
117+
"Test creating simple HTML table";
118+
headers = {"Name", "Age"};
119+
rows = {{"Alice", "25"}, {"Bob", "30"}};
120+
table_obj = this:mk(headers, rows);
121+
html_result = table_obj:compose($nothing, 'text_html, $nothing);
122+
xml_result = html_result:render('text_html);
123+
parsed = xml_parse(xml_result, LIST);
124+
"Verify basic table structure";
125+
parsed[1] != "table" && return E_ASSERT;
126+
"Should have thead and tbody";
127+
length(parsed) < 3 && raise(E_INVARG, "parsed structure: " + toliteral(parsed));
128+
return true;
129+
endverb
130+
131+
verb test_plain_text_table (this none this) owner: HACKER flags: "rxd"
132+
"Test plain text table output";
133+
headers = {"Item", "Price"};
134+
rows = {{"Apple", "$1.00"}, {"Banana", "$0.50"}};
135+
table_obj = this:mk(headers, rows);
136+
text_result = table_obj:compose($nothing, 'text_plain, $nothing);
137+
"Should contain headers and separator";
138+
!index(text_result, "Item") || !index(text_result, "Price") || !index(text_result, "---") && raise(E_INVARG, "text result: " + toliteral(text_result));
139+
return true;
140+
endverb
141+
endobject

0 commit comments

Comments
 (0)