We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
dice: .choose output is messy
I started this thought process after noticing that `.choose thing1, thing2, thing3` will yield choices that include the leading space (`thing1`, ` thing2`, and ` thing3`), but it's not that simple.

Since `.choose` just uses a simple `str.split()` as-is, without cleaning the values at all, it's already starting with messy data. Then it swaps the delimiter used for no particular reason ([comment claims](https://github.com/sopel-irc/sopel/blob/d73a410717d6cb2b164bf5411f20a995210bbc22/sopel/modules/dice.py#L248) it's "to prevent ambiguity", but I think it actually does the opposite).

What comes out the other end:

```
<dgw> .choose thing1, thing2, thing3
<Sopel> dgw: Your options: thing1|  thing2|  thing3. My choice:  thing2
```

Extra spaces everywhere, delimiters that don't really make sense with the spacing around them… Ugly.

Current plans to sort this out:

  - [x] **In 6.6.0:** Clean the input to avoid extra spaces in the output. ~~Maybe switch to always use `, `&nbsp;as delimiter in output.~~ [#1421]
  - [x] **In 7.0.0:** ~~Drop support for everything but `,` (and maybe `/`) in the input, and~~ always use `, `&nbsp;in output ~~(if not already changed for 6.6.0)~~. [#1425]

There's considerable lead time before Sopel 7, so any user objections to dropping `\` and `|` as supported delimiters may be dropped in the comments here, or in the comments of the PR I will open with the change some time in early 2019, after 6.6.0 is out.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `sopel/modules/dice.py`
Content:
```
1 # coding=utf-8
2 """
3 dice.py - Dice Module
4 Copyright 2010-2013, Dimitri "Tyrope" Molenaars, TyRope.nl
5 Copyright 2013, Ari Koivula, <ari@koivu.la>
6 Licensed under the Eiffel Forum License 2.
7 
8 https://sopel.chat/
9 """
10 from __future__ import unicode_literals, absolute_import, print_function, division
11 
12 import random
13 import re
14 import operator
15 
16 import sopel.module
17 from sopel.tools.calculation import eval_equation
18 
19 
20 class DicePouch:
21     def __init__(self, num_of_die, type_of_die, addition):
22         """Initialize dice pouch and roll the dice.
23 
24         Args:
25             num_of_die: number of dice in the pouch.
26             type_of_die: how many faces the dice have.
27             addition: how much is added to the result of the dice.
28         """
29         self.num = num_of_die
30         self.type = type_of_die
31         self.addition = addition
32 
33         self.dice = {}
34         self.dropped = {}
35 
36         self.roll_dice()
37 
38     def roll_dice(self):
39         """Roll all the dice in the pouch."""
40         self.dice = {}
41         self.dropped = {}
42         for __ in range(self.num):
43             number = random.randint(1, self.type)
44             count = self.dice.setdefault(number, 0)
45             self.dice[number] = count + 1
46 
47     def drop_lowest(self, n):
48         """Drop n lowest dice from the result.
49 
50         Args:
51             n: the number of dice to drop.
52         """
53 
54         sorted_x = sorted(self.dice.items(), key=operator.itemgetter(0))
55 
56         for i, count in sorted_x:
57             count = self.dice[i]
58             if n == 0:
59                 break
60             elif n < count:
61                 self.dice[i] = count - n
62                 self.dropped[i] = n
63                 break
64             else:
65                 self.dice[i] = 0
66                 self.dropped[i] = count
67                 n = n - count
68 
69         for i, count in self.dropped.items():
70             if self.dice[i] == 0:
71                 del self.dice[i]
72 
73     def get_simple_string(self):
74         """Return the values of the dice like (2+2+2[+1+1])+1."""
75         dice = self.dice.items()
76         faces = ("+".join([str(face)] * times) for face, times in dice)
77         dice_str = "+".join(faces)
78 
79         dropped_str = ""
80         if self.dropped:
81             dropped = self.dropped.items()
82             dfaces = ("+".join([str(face)] * times) for face, times in dropped)
83             dropped_str = "[+%s]" % ("+".join(dfaces),)
84 
85         plus_str = ""
86         if self.addition:
87             plus_str = "{:+d}".format(self.addition)
88 
89         return "(%s%s)%s" % (dice_str, dropped_str, plus_str)
90 
91     def get_compressed_string(self):
92         """Return the values of the dice like (3x2[+2x1])+1."""
93         dice = self.dice.items()
94         faces = ("%dx%d" % (times, face) for face, times in dice)
95         dice_str = "+".join(faces)
96 
97         dropped_str = ""
98         if self.dropped:
99             dropped = self.dropped.items()
100             dfaces = ("%dx%d" % (times, face) for face, times in dropped)
101             dropped_str = "[+%s]" % ("+".join(dfaces),)
102 
103         plus_str = ""
104         if self.addition:
105             plus_str = "{:+d}".format(self.addition)
106 
107         return "(%s%s)%s" % (dice_str, dropped_str, plus_str)
108 
109     def get_sum(self):
110         """Get the sum of non-dropped dice and the addition."""
111         result = self.addition
112         for face, times in self.dice.items():
113             result += face * times
114         return result
115 
116     def get_number_of_faces(self):
117         """Returns sum of different faces for dropped and not dropped dice
118 
119         This can be used to estimate, whether the result can be shown in
120         compressed form in a reasonable amount of space.
121         """
122         return len(self.dice) + len(self.dropped)
123 
124 
125 def _roll_dice(bot, dice_expression):
126     result = re.search(
127         r"""
128         (?P<dice_num>-?\d*)
129         d
130         (?P<dice_type>-?\d+)
131         (v(?P<drop_lowest>-?\d+))?
132         $""",
133         dice_expression,
134         re.IGNORECASE | re.VERBOSE)
135 
136     dice_num = int(result.group('dice_num') or 1)
137     dice_type = int(result.group('dice_type'))
138 
139     # Dice can't have zero or a negative number of sides.
140     if dice_type <= 0:
141         bot.reply("I don't have any dice with %d sides. =(" % dice_type)
142         return None  # Signal there was a problem
143 
144     # Can't roll a negative number of dice.
145     if dice_num < 0:
146         bot.reply("I'd rather not roll a negative amount of dice. =(")
147         return None  # Signal there was a problem
148 
149     # Upper limit for dice should be at most a million. Creating a dict with
150     # more than a million elements already takes a noticeable amount of time
151     # on a fast computer and ~55kB of memory.
152     if dice_num > 1000:
153         bot.reply('I only have 1000 dice. =(')
154         return None  # Signal there was a problem
155 
156     dice = DicePouch(dice_num, dice_type, 0)
157 
158     if result.group('drop_lowest'):
159         drop = int(result.group('drop_lowest'))
160         if drop >= 0:
161             dice.drop_lowest(drop)
162         else:
163             bot.reply("I can't drop the lowest %d dice. =(" % drop)
164 
165     return dice
166 
167 
168 @sopel.module.commands("roll")
169 @sopel.module.commands("dice")
170 @sopel.module.commands("d")
171 @sopel.module.priority("medium")
172 @sopel.module.example(".roll 3d1+1", 'You roll 3d1+1: (1+1+1)+1 = 4')
173 @sopel.module.example(".roll 3d1v2+1", 'You roll 3d1v2+1: (1[+1+1])+1 = 2')
174 @sopel.module.example(".roll 2d4", r'You roll 2d4: \(\d\+\d\) = \d', re=True)
175 @sopel.module.example(".roll 100d1", r'[^:]*: \(100x1\) = 100', re=True)
176 @sopel.module.example(".roll 1001d1", 'I only have 1000 dice. =(')
177 @sopel.module.example(".roll 1d1 + 1d1", 'You roll 1d1 + 1d1: (1) + (1) = 2')
178 @sopel.module.example(".roll 1d1+1d1", 'You roll 1d1+1d1: (1)+(1) = 2')
179 def roll(bot, trigger):
180     """.dice XdY[vZ][+N], rolls dice and reports the result.
181 
182     X is the number of dice. Y is the number of faces in the dice. Z is the
183     number of lowest dice to be dropped from the result. N is the constant to
184     be applied to the end result.
185     """
186     # This regexp is only allowed to have one captured group, because having
187     # more would alter the output of re.findall.
188     dice_regexp = r"-?\d*[dD]-?\d+(?:[vV]-?\d+)?"
189 
190     # Get a list of all dice expressions, evaluate them and then replace the
191     # expressions in the original string with the results. Replacing is done
192     # using string formatting, so %-characters must be escaped.
193     if not trigger.group(2):
194         return bot.reply("No dice to roll.")
195     arg_str = trigger.group(2)
196     dice_expressions = re.findall(dice_regexp, arg_str)
197     arg_str = arg_str.replace("%", "%%")
198     arg_str = re.sub(dice_regexp, "%s", arg_str)
199 
200     f = lambda dice_expr: _roll_dice(bot, dice_expr)
201     dice = list(map(f, dice_expressions))
202 
203     if None in dice:
204         # Stop computing roll if there was a problem rolling dice.
205         return
206 
207     def _get_eval_str(dice):
208         return "(%d)" % (dice.get_sum(),)
209 
210     def _get_pretty_str(dice):
211         if dice.num <= 10:
212             return dice.get_simple_string()
213         elif dice.get_number_of_faces() <= 10:
214             return dice.get_compressed_string()
215         else:
216             return "(...)"
217 
218     eval_str = arg_str % (tuple(map(_get_eval_str, dice)))
219     pretty_str = arg_str % (tuple(map(_get_pretty_str, dice)))
220 
221     try:
222         result = eval_equation(eval_str)
223     except TypeError:
224         bot.reply("The type of this equation is, apparently, not a string. " +
225             "How did you do that, anyway?")
226     except ValueError:
227         # As it seems that ValueError is raised if the resulting equation would
228         # be too big, give a semi-serious answer to reflect on this.
229         bot.reply("You roll %s: %s = very big" % (
230             trigger.group(2), pretty_str))
231         return
232     except (SyntaxError, eval_equation.Error):
233         bot.reply("I don't know how to process that. " +
234             "Are the dice as well as the algorithms correct?")
235         return
236 
237     bot.reply("You roll %s: %s = %d" % (
238         trigger.group(2), pretty_str, result))
239 
240 
241 @sopel.module.commands("choice")
242 @sopel.module.commands("ch")
243 @sopel.module.commands("choose")
244 @sopel.module.priority("medium")
245 def choose(bot, trigger):
246     """
247     .choice option1|option2|option3 - Makes a difficult choice easy.
248     """
249     if not trigger.group(2):
250         return bot.reply('I\'d choose an option, but you didn\'t give me any.')
251     choices = [trigger.group(2)]
252     for delim in '|\\/,':
253         choices = trigger.group(2).split(delim)
254         if len(choices) > 1:
255             break
256     choices = [choice.strip() for choice in choices]
257     # Use a different delimiter in the output, to prevent ambiguity.
258     for show_delim in ',|/\\':
259         if show_delim not in trigger.group(2):
260             show_delim += ' '
261             break
262 
263     pick = random.choice(choices)
264     return bot.reply('Your options: %s. My choice: %s' % (show_delim.join(choices), pick))
265 
266 
267 if __name__ == "__main__":
268     from sopel.test_tools import run_example_tests
269     run_example_tests(__file__)
270
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/sopel/modules/dice.py b/sopel/modules/dice.py
--- a/sopel/modules/dice.py
+++ b/sopel/modules/dice.py
@@ -242,6 +242,15 @@
 @sopel.module.commands("ch")
 @sopel.module.commands("choose")
 @sopel.module.priority("medium")
+@sopel.module.example(".choose a, b, c", r'Your options: a, b, c. My choice: (a|b|c)', re=True)
+@sopel.module.example(".choose a | b | c", r'Your options: a, b, c. My choice: (a|b|c)', re=True)
+@sopel.module.example(".choose a,b,c", r'Your options: a, b, c. My choice: (a|b|c)', re=True)
+@sopel.module.example(".choose a|b|c", r'Your options: a, b, c. My choice: (a|b|c)', re=True)
+@sopel.module.example(".choose a b c", r'Your options: a, b, c. My choice: (a|b|c)', re=True)
+@sopel.module.example(".choose a, b | just a",
+                      r'Your options: "a, b", just a. My choice: ((a, b)|(just a))',
+                      re=True)
+@sopel.module.example(".choose a", 'Your options: a. My choice: a')
 def choose(bot, trigger):
     """
     .choice option1|option2|option3 - Makes a difficult choice easy.
@@ -249,19 +258,19 @@
     if not trigger.group(2):
         return bot.reply('I\'d choose an option, but you didn\'t give me any.')
     choices = [trigger.group(2)]
-    for delim in '|\\/,':
+    for delim in '|\\/, ':
         choices = trigger.group(2).split(delim)
         if len(choices) > 1:
             break
     choices = [choice.strip() for choice in choices]
-    # Use a different delimiter in the output, to prevent ambiguity.
-    for show_delim in ',|/\\':
-        if show_delim not in trigger.group(2):
-            show_delim += ' '
-            break
-
     pick = random.choice(choices)
-    return bot.reply('Your options: %s. My choice: %s' % (show_delim.join(choices), pick))
+
+    # Always use a comma in the output
+    display_options = ', '.join(
+        choice if ',' not in choice else '"%s"' % choice
+        for choice in choices
+    )
+    return bot.reply('Your options: %s. My choice: %s' % (display_options, pick))
 
 
 if __name__ == "__main__":

{"golden_diff": "diff --git a/sopel/modules/dice.py b/sopel/modules/dice.py\n--- a/sopel/modules/dice.py\n+++ b/sopel/modules/dice.py\n@@ -242,6 +242,15 @@\n @sopel.module.commands(\"ch\")\n @sopel.module.commands(\"choose\")\n @sopel.module.priority(\"medium\")\n+@sopel.module.example(\".choose a, b, c\", r'Your options: a, b, c. My choice: (a|b|c)', re=True)\n+@sopel.module.example(\".choose a | b | c\", r'Your options: a, b, c. My choice: (a|b|c)', re=True)\n+@sopel.module.example(\".choose a,b,c\", r'Your options: a, b, c. My choice: (a|b|c)', re=True)\n+@sopel.module.example(\".choose a|b|c\", r'Your options: a, b, c. My choice: (a|b|c)', re=True)\n+@sopel.module.example(\".choose a b c\", r'Your options: a, b, c. My choice: (a|b|c)', re=True)\n+@sopel.module.example(\".choose a, b | just a\",\n+                      r'Your options: \"a, b\", just a. My choice: ((a, b)|(just a))',\n+                      re=True)\n+@sopel.module.example(\".choose a\", 'Your options: a. My choice: a')\n def choose(bot, trigger):\n     \"\"\"\n     .choice option1|option2|option3 - Makes a difficult choice easy.\n@@ -249,19 +258,19 @@\n     if not trigger.group(2):\n         return bot.reply('I\\'d choose an option, but you didn\\'t give me any.')\n     choices = [trigger.group(2)]\n-    for delim in '|\\\\/,':\n+    for delim in '|\\\\/, ':\n         choices = trigger.group(2).split(delim)\n         if len(choices) > 1:\n             break\n     choices = [choice.strip() for choice in choices]\n-    # Use a different delimiter in the output, to prevent ambiguity.\n-    for show_delim in ',|/\\\\':\n-        if show_delim not in trigger.group(2):\n-            show_delim += ' '\n-            break\n-\n     pick = random.choice(choices)\n-    return bot.reply('Your options: %s. My choice: %s' % (show_delim.join(choices), pick))\n+\n+    # Always use a comma in the output\n+    display_options = ', '.join(\n+        choice if ',' not in choice else '\"%s\"' % choice\n+        for choice in choices\n+    )\n+    return bot.reply('Your options: %s. My choice: %s' % (display_options, pick))\n \n \n if __name__ == \"__main__\":\n", "issue": "dice: .choose output is messy\nI started this thought process after noticing that `.choose thing1, thing2, thing3` will yield choices that include the leading space (`thing1`, ` thing2`, and ` thing3`), but it's not that simple.\r\n\r\nSince `.choose` just uses a simple `str.split()` as-is, without cleaning the values at all, it's already starting with messy data. Then it swaps the delimiter used for no particular reason ([comment claims](https://github.com/sopel-irc/sopel/blob/d73a410717d6cb2b164bf5411f20a995210bbc22/sopel/modules/dice.py#L248) it's \"to prevent ambiguity\", but I think it actually does the opposite).\r\n\r\nWhat comes out the other end:\r\n\r\n```\r\n<dgw> .choose thing1, thing2, thing3\r\n<Sopel> dgw: Your options: thing1|  thing2|  thing3. My choice:  thing2\r\n```\r\n\r\nExtra spaces everywhere, delimiters that don't really make sense with the spacing around them\u2026 Ugly.\r\n\r\nCurrent plans to sort this out:\r\n\r\n  - [x] **In 6.6.0:** Clean the input to avoid extra spaces in the output. ~~Maybe switch to always use `, `&nbsp;as delimiter in output.~~ [#1421]\r\n  - [x] **In 7.0.0:** ~~Drop support for everything but `,` (and maybe `/`) in the input, and~~ always use `, `&nbsp;in output ~~(if not already changed for 6.6.0)~~. [#1425]\r\n\r\nThere's considerable lead time before Sopel 7, so any user objections to dropping `\\` and `|` as supported delimiters may be dropped in the comments here, or in the comments of the PR I will open with the change some time in early 2019, after 6.6.0 is out.\n", "before_files": [{"content": "# coding=utf-8\n\"\"\"\ndice.py - Dice Module\nCopyright 2010-2013, Dimitri \"Tyrope\" Molenaars, TyRope.nl\nCopyright 2013, Ari Koivula, <ari@koivu.la>\nLicensed under the Eiffel Forum License 2.\n\nhttps://sopel.chat/\n\"\"\"\nfrom __future__ import unicode_literals, absolute_import, print_function, division\n\nimport random\nimport re\nimport operator\n\nimport sopel.module\nfrom sopel.tools.calculation import eval_equation\n\n\nclass DicePouch:\n    def __init__(self, num_of_die, type_of_die, addition):\n        \"\"\"Initialize dice pouch and roll the dice.\n\n        Args:\n            num_of_die: number of dice in the pouch.\n            type_of_die: how many faces the dice have.\n            addition: how much is added to the result of the dice.\n        \"\"\"\n        self.num = num_of_die\n        self.type = type_of_die\n        self.addition = addition\n\n        self.dice = {}\n        self.dropped = {}\n\n        self.roll_dice()\n\n    def roll_dice(self):\n        \"\"\"Roll all the dice in the pouch.\"\"\"\n        self.dice = {}\n        self.dropped = {}\n        for __ in range(self.num):\n            number = random.randint(1, self.type)\n            count = self.dice.setdefault(number, 0)\n            self.dice[number] = count + 1\n\n    def drop_lowest(self, n):\n        \"\"\"Drop n lowest dice from the result.\n\n        Args:\n            n: the number of dice to drop.\n        \"\"\"\n\n        sorted_x = sorted(self.dice.items(), key=operator.itemgetter(0))\n\n        for i, count in sorted_x:\n            count = self.dice[i]\n            if n == 0:\n                break\n            elif n < count:\n                self.dice[i] = count - n\n                self.dropped[i] = n\n                break\n            else:\n                self.dice[i] = 0\n                self.dropped[i] = count\n                n = n - count\n\n        for i, count in self.dropped.items():\n            if self.dice[i] == 0:\n                del self.dice[i]\n\n    def get_simple_string(self):\n        \"\"\"Return the values of the dice like (2+2+2[+1+1])+1.\"\"\"\n        dice = self.dice.items()\n        faces = (\"+\".join([str(face)] * times) for face, times in dice)\n        dice_str = \"+\".join(faces)\n\n        dropped_str = \"\"\n        if self.dropped:\n            dropped = self.dropped.items()\n            dfaces = (\"+\".join([str(face)] * times) for face, times in dropped)\n            dropped_str = \"[+%s]\" % (\"+\".join(dfaces),)\n\n        plus_str = \"\"\n        if self.addition:\n            plus_str = \"{:+d}\".format(self.addition)\n\n        return \"(%s%s)%s\" % (dice_str, dropped_str, plus_str)\n\n    def get_compressed_string(self):\n        \"\"\"Return the values of the dice like (3x2[+2x1])+1.\"\"\"\n        dice = self.dice.items()\n        faces = (\"%dx%d\" % (times, face) for face, times in dice)\n        dice_str = \"+\".join(faces)\n\n        dropped_str = \"\"\n        if self.dropped:\n            dropped = self.dropped.items()\n            dfaces = (\"%dx%d\" % (times, face) for face, times in dropped)\n            dropped_str = \"[+%s]\" % (\"+\".join(dfaces),)\n\n        plus_str = \"\"\n        if self.addition:\n            plus_str = \"{:+d}\".format(self.addition)\n\n        return \"(%s%s)%s\" % (dice_str, dropped_str, plus_str)\n\n    def get_sum(self):\n        \"\"\"Get the sum of non-dropped dice and the addition.\"\"\"\n        result = self.addition\n        for face, times in self.dice.items():\n            result += face * times\n        return result\n\n    def get_number_of_faces(self):\n        \"\"\"Returns sum of different faces for dropped and not dropped dice\n\n        This can be used to estimate, whether the result can be shown in\n        compressed form in a reasonable amount of space.\n        \"\"\"\n        return len(self.dice) + len(self.dropped)\n\n\ndef _roll_dice(bot, dice_expression):\n    result = re.search(\n        r\"\"\"\n        (?P<dice_num>-?\\d*)\n        d\n        (?P<dice_type>-?\\d+)\n        (v(?P<drop_lowest>-?\\d+))?\n        $\"\"\",\n        dice_expression,\n        re.IGNORECASE | re.VERBOSE)\n\n    dice_num = int(result.group('dice_num') or 1)\n    dice_type = int(result.group('dice_type'))\n\n    # Dice can't have zero or a negative number of sides.\n    if dice_type <= 0:\n        bot.reply(\"I don't have any dice with %d sides. =(\" % dice_type)\n        return None  # Signal there was a problem\n\n    # Can't roll a negative number of dice.\n    if dice_num < 0:\n        bot.reply(\"I'd rather not roll a negative amount of dice. =(\")\n        return None  # Signal there was a problem\n\n    # Upper limit for dice should be at most a million. Creating a dict with\n    # more than a million elements already takes a noticeable amount of time\n    # on a fast computer and ~55kB of memory.\n    if dice_num > 1000:\n        bot.reply('I only have 1000 dice. =(')\n        return None  # Signal there was a problem\n\n    dice = DicePouch(dice_num, dice_type, 0)\n\n    if result.group('drop_lowest'):\n        drop = int(result.group('drop_lowest'))\n        if drop >= 0:\n            dice.drop_lowest(drop)\n        else:\n            bot.reply(\"I can't drop the lowest %d dice. =(\" % drop)\n\n    return dice\n\n\n@sopel.module.commands(\"roll\")\n@sopel.module.commands(\"dice\")\n@sopel.module.commands(\"d\")\n@sopel.module.priority(\"medium\")\n@sopel.module.example(\".roll 3d1+1\", 'You roll 3d1+1: (1+1+1)+1 = 4')\n@sopel.module.example(\".roll 3d1v2+1\", 'You roll 3d1v2+1: (1[+1+1])+1 = 2')\n@sopel.module.example(\".roll 2d4\", r'You roll 2d4: \\(\\d\\+\\d\\) = \\d', re=True)\n@sopel.module.example(\".roll 100d1\", r'[^:]*: \\(100x1\\) = 100', re=True)\n@sopel.module.example(\".roll 1001d1\", 'I only have 1000 dice. =(')\n@sopel.module.example(\".roll 1d1 + 1d1\", 'You roll 1d1 + 1d1: (1) + (1) = 2')\n@sopel.module.example(\".roll 1d1+1d1\", 'You roll 1d1+1d1: (1)+(1) = 2')\ndef roll(bot, trigger):\n    \"\"\".dice XdY[vZ][+N], rolls dice and reports the result.\n\n    X is the number of dice. Y is the number of faces in the dice. Z is the\n    number of lowest dice to be dropped from the result. N is the constant to\n    be applied to the end result.\n    \"\"\"\n    # This regexp is only allowed to have one captured group, because having\n    # more would alter the output of re.findall.\n    dice_regexp = r\"-?\\d*[dD]-?\\d+(?:[vV]-?\\d+)?\"\n\n    # Get a list of all dice expressions, evaluate them and then replace the\n    # expressions in the original string with the results. Replacing is done\n    # using string formatting, so %-characters must be escaped.\n    if not trigger.group(2):\n        return bot.reply(\"No dice to roll.\")\n    arg_str = trigger.group(2)\n    dice_expressions = re.findall(dice_regexp, arg_str)\n    arg_str = arg_str.replace(\"%\", \"%%\")\n    arg_str = re.sub(dice_regexp, \"%s\", arg_str)\n\n    f = lambda dice_expr: _roll_dice(bot, dice_expr)\n    dice = list(map(f, dice_expressions))\n\n    if None in dice:\n        # Stop computing roll if there was a problem rolling dice.\n        return\n\n    def _get_eval_str(dice):\n        return \"(%d)\" % (dice.get_sum(),)\n\n    def _get_pretty_str(dice):\n        if dice.num <= 10:\n            return dice.get_simple_string()\n        elif dice.get_number_of_faces() <= 10:\n            return dice.get_compressed_string()\n        else:\n            return \"(...)\"\n\n    eval_str = arg_str % (tuple(map(_get_eval_str, dice)))\n    pretty_str = arg_str % (tuple(map(_get_pretty_str, dice)))\n\n    try:\n        result = eval_equation(eval_str)\n    except TypeError:\n        bot.reply(\"The type of this equation is, apparently, not a string. \" +\n            \"How did you do that, anyway?\")\n    except ValueError:\n        # As it seems that ValueError is raised if the resulting equation would\n        # be too big, give a semi-serious answer to reflect on this.\n        bot.reply(\"You roll %s: %s = very big\" % (\n            trigger.group(2), pretty_str))\n        return\n    except (SyntaxError, eval_equation.Error):\n        bot.reply(\"I don't know how to process that. \" +\n            \"Are the dice as well as the algorithms correct?\")\n        return\n\n    bot.reply(\"You roll %s: %s = %d\" % (\n        trigger.group(2), pretty_str, result))\n\n\n@sopel.module.commands(\"choice\")\n@sopel.module.commands(\"ch\")\n@sopel.module.commands(\"choose\")\n@sopel.module.priority(\"medium\")\ndef choose(bot, trigger):\n    \"\"\"\n    .choice option1|option2|option3 - Makes a difficult choice easy.\n    \"\"\"\n    if not trigger.group(2):\n        return bot.reply('I\\'d choose an option, but you didn\\'t give me any.')\n    choices = [trigger.group(2)]\n    for delim in '|\\\\/,':\n        choices = trigger.group(2).split(delim)\n        if len(choices) > 1:\n            break\n    choices = [choice.strip() for choice in choices]\n    # Use a different delimiter in the output, to prevent ambiguity.\n    for show_delim in ',|/\\\\':\n        if show_delim not in trigger.group(2):\n            show_delim += ' '\n            break\n\n    pick = random.choice(choices)\n    return bot.reply('Your options: %s. My choice: %s' % (show_delim.join(choices), pick))\n\n\nif __name__ == \"__main__\":\n    from sopel.test_tools import run_example_tests\n    run_example_tests(__file__)\n", "path": "sopel/modules/dice.py"}], "after_files": [{"content": "# coding=utf-8\n\"\"\"\ndice.py - Dice Module\nCopyright 2010-2013, Dimitri \"Tyrope\" Molenaars, TyRope.nl\nCopyright 2013, Ari Koivula, <ari@koivu.la>\nLicensed under the Eiffel Forum License 2.\n\nhttps://sopel.chat/\n\"\"\"\nfrom __future__ import unicode_literals, absolute_import, print_function, division\n\nimport random\nimport re\nimport operator\n\nimport sopel.module\nfrom sopel.tools.calculation import eval_equation\n\n\nclass DicePouch:\n    def __init__(self, num_of_die, type_of_die, addition):\n        \"\"\"Initialize dice pouch and roll the dice.\n\n        Args:\n            num_of_die: number of dice in the pouch.\n            type_of_die: how many faces the dice have.\n            addition: how much is added to the result of the dice.\n        \"\"\"\n        self.num = num_of_die\n        self.type = type_of_die\n        self.addition = addition\n\n        self.dice = {}\n        self.dropped = {}\n\n        self.roll_dice()\n\n    def roll_dice(self):\n        \"\"\"Roll all the dice in the pouch.\"\"\"\n        self.dice = {}\n        self.dropped = {}\n        for __ in range(self.num):\n            number = random.randint(1, self.type)\n            count = self.dice.setdefault(number, 0)\n            self.dice[number] = count + 1\n\n    def drop_lowest(self, n):\n        \"\"\"Drop n lowest dice from the result.\n\n        Args:\n            n: the number of dice to drop.\n        \"\"\"\n\n        sorted_x = sorted(self.dice.items(), key=operator.itemgetter(0))\n\n        for i, count in sorted_x:\n            count = self.dice[i]\n            if n == 0:\n                break\n            elif n < count:\n                self.dice[i] = count - n\n                self.dropped[i] = n\n                break\n            else:\n                self.dice[i] = 0\n                self.dropped[i] = count\n                n = n - count\n\n        for i, count in self.dropped.items():\n            if self.dice[i] == 0:\n                del self.dice[i]\n\n    def get_simple_string(self):\n        \"\"\"Return the values of the dice like (2+2+2[+1+1])+1.\"\"\"\n        dice = self.dice.items()\n        faces = (\"+\".join([str(face)] * times) for face, times in dice)\n        dice_str = \"+\".join(faces)\n\n        dropped_str = \"\"\n        if self.dropped:\n            dropped = self.dropped.items()\n            dfaces = (\"+\".join([str(face)] * times) for face, times in dropped)\n            dropped_str = \"[+%s]\" % (\"+\".join(dfaces),)\n\n        plus_str = \"\"\n        if self.addition:\n            plus_str = \"{:+d}\".format(self.addition)\n\n        return \"(%s%s)%s\" % (dice_str, dropped_str, plus_str)\n\n    def get_compressed_string(self):\n        \"\"\"Return the values of the dice like (3x2[+2x1])+1.\"\"\"\n        dice = self.dice.items()\n        faces = (\"%dx%d\" % (times, face) for face, times in dice)\n        dice_str = \"+\".join(faces)\n\n        dropped_str = \"\"\n        if self.dropped:\n            dropped = self.dropped.items()\n            dfaces = (\"%dx%d\" % (times, face) for face, times in dropped)\n            dropped_str = \"[+%s]\" % (\"+\".join(dfaces),)\n\n        plus_str = \"\"\n        if self.addition:\n            plus_str = \"{:+d}\".format(self.addition)\n\n        return \"(%s%s)%s\" % (dice_str, dropped_str, plus_str)\n\n    def get_sum(self):\n        \"\"\"Get the sum of non-dropped dice and the addition.\"\"\"\n        result = self.addition\n        for face, times in self.dice.items():\n            result += face * times\n        return result\n\n    def get_number_of_faces(self):\n        \"\"\"Returns sum of different faces for dropped and not dropped dice\n\n        This can be used to estimate, whether the result can be shown in\n        compressed form in a reasonable amount of space.\n        \"\"\"\n        return len(self.dice) + len(self.dropped)\n\n\ndef _roll_dice(bot, dice_expression):\n    result = re.search(\n        r\"\"\"\n        (?P<dice_num>-?\\d*)\n        d\n        (?P<dice_type>-?\\d+)\n        (v(?P<drop_lowest>-?\\d+))?\n        $\"\"\",\n        dice_expression,\n        re.IGNORECASE | re.VERBOSE)\n\n    dice_num = int(result.group('dice_num') or 1)\n    dice_type = int(result.group('dice_type'))\n\n    # Dice can't have zero or a negative number of sides.\n    if dice_type <= 0:\n        bot.reply(\"I don't have any dice with %d sides. =(\" % dice_type)\n        return None  # Signal there was a problem\n\n    # Can't roll a negative number of dice.\n    if dice_num < 0:\n        bot.reply(\"I'd rather not roll a negative amount of dice. =(\")\n        return None  # Signal there was a problem\n\n    # Upper limit for dice should be at most a million. Creating a dict with\n    # more than a million elements already takes a noticeable amount of time\n    # on a fast computer and ~55kB of memory.\n    if dice_num > 1000:\n        bot.reply('I only have 1000 dice. =(')\n        return None  # Signal there was a problem\n\n    dice = DicePouch(dice_num, dice_type, 0)\n\n    if result.group('drop_lowest'):\n        drop = int(result.group('drop_lowest'))\n        if drop >= 0:\n            dice.drop_lowest(drop)\n        else:\n            bot.reply(\"I can't drop the lowest %d dice. =(\" % drop)\n\n    return dice\n\n\n@sopel.module.commands(\"roll\")\n@sopel.module.commands(\"dice\")\n@sopel.module.commands(\"d\")\n@sopel.module.priority(\"medium\")\n@sopel.module.example(\".roll 3d1+1\", 'You roll 3d1+1: (1+1+1)+1 = 4')\n@sopel.module.example(\".roll 3d1v2+1\", 'You roll 3d1v2+1: (1[+1+1])+1 = 2')\n@sopel.module.example(\".roll 2d4\", r'You roll 2d4: \\(\\d\\+\\d\\) = \\d', re=True)\n@sopel.module.example(\".roll 100d1\", r'[^:]*: \\(100x1\\) = 100', re=True)\n@sopel.module.example(\".roll 1001d1\", 'I only have 1000 dice. =(')\n@sopel.module.example(\".roll 1d1 + 1d1\", 'You roll 1d1 + 1d1: (1) + (1) = 2')\n@sopel.module.example(\".roll 1d1+1d1\", 'You roll 1d1+1d1: (1)+(1) = 2')\ndef roll(bot, trigger):\n    \"\"\".dice XdY[vZ][+N], rolls dice and reports the result.\n\n    X is the number of dice. Y is the number of faces in the dice. Z is the\n    number of lowest dice to be dropped from the result. N is the constant to\n    be applied to the end result.\n    \"\"\"\n    # This regexp is only allowed to have one captured group, because having\n    # more would alter the output of re.findall.\n    dice_regexp = r\"-?\\d*[dD]-?\\d+(?:[vV]-?\\d+)?\"\n\n    # Get a list of all dice expressions, evaluate them and then replace the\n    # expressions in the original string with the results. Replacing is done\n    # using string formatting, so %-characters must be escaped.\n    if not trigger.group(2):\n        return bot.reply(\"No dice to roll.\")\n    arg_str = trigger.group(2)\n    dice_expressions = re.findall(dice_regexp, arg_str)\n    arg_str = arg_str.replace(\"%\", \"%%\")\n    arg_str = re.sub(dice_regexp, \"%s\", arg_str)\n\n    f = lambda dice_expr: _roll_dice(bot, dice_expr)\n    dice = list(map(f, dice_expressions))\n\n    if None in dice:\n        # Stop computing roll if there was a problem rolling dice.\n        return\n\n    def _get_eval_str(dice):\n        return \"(%d)\" % (dice.get_sum(),)\n\n    def _get_pretty_str(dice):\n        if dice.num <= 10:\n            return dice.get_simple_string()\n        elif dice.get_number_of_faces() <= 10:\n            return dice.get_compressed_string()\n        else:\n            return \"(...)\"\n\n    eval_str = arg_str % (tuple(map(_get_eval_str, dice)))\n    pretty_str = arg_str % (tuple(map(_get_pretty_str, dice)))\n\n    try:\n        result = eval_equation(eval_str)\n    except TypeError:\n        bot.reply(\"The type of this equation is, apparently, not a string. \" +\n            \"How did you do that, anyway?\")\n    except ValueError:\n        # As it seems that ValueError is raised if the resulting equation would\n        # be too big, give a semi-serious answer to reflect on this.\n        bot.reply(\"You roll %s: %s = very big\" % (\n            trigger.group(2), pretty_str))\n        return\n    except (SyntaxError, eval_equation.Error):\n        bot.reply(\"I don't know how to process that. \" +\n            \"Are the dice as well as the algorithms correct?\")\n        return\n\n    bot.reply(\"You roll %s: %s = %d\" % (\n        trigger.group(2), pretty_str, result))\n\n\n@sopel.module.commands(\"choice\")\n@sopel.module.commands(\"ch\")\n@sopel.module.commands(\"choose\")\n@sopel.module.priority(\"medium\")\n@sopel.module.example(\".choose a, b, c\", r'Your options: a, b, c. My choice: (a|b|c)', re=True)\n@sopel.module.example(\".choose a | b | c\", r'Your options: a, b, c. My choice: (a|b|c)', re=True)\n@sopel.module.example(\".choose a,b,c\", r'Your options: a, b, c. My choice: (a|b|c)', re=True)\n@sopel.module.example(\".choose a|b|c\", r'Your options: a, b, c. My choice: (a|b|c)', re=True)\n@sopel.module.example(\".choose a b c\", r'Your options: a, b, c. My choice: (a|b|c)', re=True)\n@sopel.module.example(\".choose a, b | just a\",\n                      r'Your options: \"a, b\", just a. My choice: ((a, b)|(just a))',\n                      re=True)\n@sopel.module.example(\".choose a\", 'Your options: a. My choice: a')\ndef choose(bot, trigger):\n    \"\"\"\n    .choice option1|option2|option3 - Makes a difficult choice easy.\n    \"\"\"\n    if not trigger.group(2):\n        return bot.reply('I\\'d choose an option, but you didn\\'t give me any.')\n    choices = [trigger.group(2)]\n    for delim in '|\\\\/, ':\n        choices = trigger.group(2).split(delim)\n        if len(choices) > 1:\n            break\n    choices = [choice.strip() for choice in choices]\n    pick = random.choice(choices)\n\n    # Always use a comma in the output\n    display_options = ', '.join(\n        choice if ',' not in choice else '\"%s\"' % choice\n        for choice in choices\n    )\n    return bot.reply('Your options: %s. My choice: %s' % (display_options, pick))\n\n\nif __name__ == \"__main__\":\n    from sopel.test_tools import run_example_tests\n    run_example_tests(__file__)\n", "path": "sopel/modules/dice.py"}]}