1 /++
2 + Various bits common to different parts of IRC, such as channel and user
3 + metadata.
4 +/
5 module virc.common;
6 
7 import std.datetime : SysTime;
8 import std.range : isOutputRange, put;
9 import std.typecons : Flag, Nullable;
10 
11 import virc.modes;
12 import virc.usermask;
13 
14 alias RFC2812Compliance = Flag!"RFC2812Compliance";
15 /++
16 + Metadata for an IRC channel.
17 +/
18 struct Channel {
19 	///
20 	string name;
21 	void toString(T)(T sink) const if (isOutputRange!(T, const(char))) {
22 		put(sink, name);
23 	}
24 	this(string str) @safe pure nothrow @nogc {
25 		name = str;
26 	}
27 	this(string str, string modePrefixes, string chanPrefixes) @safe pure in {
28 		import std.algorithm : canFind;
29 		import std.array : front;
30 		assert(str.length > 0);
31 		assert(modePrefixes.canFind(str.front) || chanPrefixes.canFind(str.front));
32 	} do {
33 		import std.algorithm : canFind;
34 		import std.array : empty, front, popFront;
35 		if (modePrefixes.canFind(str.front)) {
36 			auto tmpCopy = str;
37 			tmpCopy.popFront();
38 			if (!tmpCopy.empty && chanPrefixes.canFind(tmpCopy.front)) {
39 				name = tmpCopy;
40 			} else {
41 				name = str;
42 			}
43 		} else {
44 			name = str;
45 		}
46 	}
47 }
48 ///
49 @safe pure unittest {
50 	assert(Channel("#test").name == "#test");
51 	assert(Channel("#test", "@%+", "#").name == "#test");
52 	assert(Channel("+test", "@%+", "+").name == "+test");
53 	assert(Channel("++test", "@%+", "+").name == "+test");
54 	assert(Channel("@+test", "@%+", "#+").name == "+test");
55 	assert(Channel("@+", "@%+", "#+").name == "+");
56 	assert(Channel("+", "@%+", "#+").name == "+");
57 }
58 /++
59 + Channel topic metadata. A message is guaranteed, but the time and user
60 + associated with setting it may not be present.
61 +/
62 struct Topic {
63 	///
64 	string message;
65 	///
66 	Nullable!SysTime time;
67 	///
68 	Nullable!User setBy;
69 	auto opEquals(const Topic b) const {
70 		return message == b.message;
71 	}
72 	///
73 	auto toHash() const {
74 		return message.hashOf;
75 	}
76 }
77 @system pure nothrow @nogc unittest {
78 	assert(Topic("Hello!").toHash == Topic("Hello!").toHash);
79 }
80 /++
81 + User metadata. User's mask will always be present, but real name's presence
82 + depends on the context. Account's presence depends on the server's
83 + capabilities.
84 +/
85 struct User {
86 	///
87 	UserMask mask;
88 	///
89 	Nullable!string realName;
90 	///
91 	Nullable!string account;
92 	///
93 	this(string str) @safe pure nothrow @nogc {
94 		mask = UserMask(str);
95 	}
96 	///
97 	this(UserMask mask_) @safe pure nothrow @nogc {
98 		mask = mask_;
99 	}
100 	///
101 	this(string nick, string ident, string host) @safe pure nothrow @nogc {
102 		mask.nickname = nick;
103 		mask.ident = ident;
104 		mask.host = host;
105 	}
106 	///
107 	auto nickname() const {
108 		return mask.nickname;
109 	}
110 	///
111 	auto ident() const {
112 		return mask.ident;
113 	}
114 	///
115 	auto host() const {
116 		return mask.host;
117 	}
118 	///
119 	auto server() const {
120 		assert(mask.ident.isNull);
121 		assert(mask.host.isNull);
122 		return mask.nickname;
123 	}
124 	void toString(T)(T sink) const if (isOutputRange!(T, const(char))) {
125 		mask.toString(sink);
126 		if (!account.isNull) {
127 			put(sink, " (");
128 			put(sink, account.get);
129 			put(sink, ")");
130 		}
131 	}
132 	auto opEquals(const User b) const {
133 		if (!this.realName.isNull && !b.realName.isNull && (this.realName != b.realName)) {
134 			return false;
135 		}
136 		if (!this.account.isNull && !b.account.isNull && (this.account != b.account)) {
137 			return false;
138 		}
139 		return this.mask == b.mask;
140 	}
141 	///
142 	auto toHash() const {
143 		return mask.hashOf;
144 	}
145 }
146 @safe pure nothrow @nogc unittest {
147 	auto user = User("Test!Testo@Testy");
148 	auto compUser = User("Test!Testo@Testy");
149 	assert(user.mask.nickname == "Test");
150 	assert(user.mask.ident == "Testo");
151 	assert(user.mask.host == "Testy");
152 	assert(user == compUser);
153 	user.realName = "TestseT";
154 	compUser.realName = "whoever";
155 	assert(user != compUser);
156 	compUser.realName = user.realName;
157 	user.account = "Testeridoo";
158 	compUser.account = "Tototo";
159 	assert(user != compUser);
160 	assert(User("Test!Testo@Testy") == User("Test", "Testo", "Testy"));
161 }
162 @safe pure unittest {
163 	import std.conv : text;
164 	auto user = User("Test!Testo@Testy");
165 	user.account = "Tester";
166 	assert(user.text == "Test!Testo@Testy (Tester)");
167 }
168 @system pure nothrow /+@nogc+/ unittest {
169 	immutable user = User("Test!Testo@Testy");
170 	immutable compUser = User("Test!Testo@Testy");
171 	assert(user.toHash == compUser.toHash);
172 }