Skip to content

Commit d6d4567

Browse files
author
Noah Petherbridge
committed
Add Python3 compatibility for RiveScript
Make the RiveScript library simultaneously compatible with Python 2 and Python 3 by importing the print_function from __future__ and reworking parts of the code to make it compatible with both versions. Implement the "null character" based placeholders for substitutions that replaces the old ROT13-based method (bringing the lib up to speed with the Perl version). Only call msg.decode('utf8') for Python 2, as this isn't needed in Python 3 due to strings being Unicode by default. Make rivescript.python more robust, giving the compile-time AND run-time error messages as console output when errors occur calling the Python object macros. Add example3.py as a Python 3 version of example.py. Update brain/python.rs's Base64 example to make it compatible with both Python 2 and Python 3. Add an "add" object macro to demonstrate the casting to str of the responses from object macros. Make rivescript.python cast to str to fix any possible sources of crashes when an object returns an int or other data type.
1 parent 2f2d500 commit d6d4567

5 files changed

Lines changed: 109 additions & 41 deletions

File tree

brain/python.rs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,33 @@
33
! version = 2.0
44

55
> object base64 python
6-
import base64 as b64
7-
return b64.b64encode(" ".join(args))
6+
import base64 as b64
7+
mess = " ".join(args)
8+
9+
# Make this function work in Python3 as well.
10+
import sys
11+
if sys.version_info[0] == 3:
12+
# Python3's Base64 requires bytes, not a str,
13+
# so encode the str into bytes.
14+
mess = mess.encode()
15+
base = b64.b64encode(mess)
16+
17+
# Return the base64 result, decoded back into a str.
18+
return base.decode()
19+
else:
20+
# Python2 is simple.
21+
return b64.b64encode(mess)
22+
< object
23+
24+
> object add python
25+
# This function returns an int, and shows that the results
26+
# from python object macros are always casted to str.
27+
a, b = args
28+
return int(a) + int(b)
829
< object
930

1031
+ encode * in base64
1132
- OK: <call>base64 <star></call>
33+
34+
+ what is # plus #
35+
- <star1> + <star2> = <call>add <star1> <star2></call>

example.py

100644100755
File mode changed.

example3.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/usr/bin/python3
2+
3+
# Python 3 example
4+
5+
from rivescript import RiveScript
6+
7+
rs = RiveScript()
8+
rs.load_directory("./brain")
9+
rs.sort_replies()
10+
11+
print("""This is a bare minimal example for how to write your own RiveScript bot!
12+
13+
For a more full-fledged example, try running: `python rivescript brain`
14+
This will run the built-in Interactive Mode of the RiveScript library. It has
15+
some more advanced features like supporting JSON for communication with the
16+
bot. See `python rivescript --help` for more info.
17+
18+
example.py is just a simple script that loads the RiveScript documents from
19+
the 'brain/' folder, and lets you chat with the bot.
20+
21+
Type /quit when you're done to exit this example.
22+
""")
23+
24+
while True:
25+
msg = input("You> ")
26+
if msg == '/quit':
27+
quit()
28+
reply = rs.reply("localuser", msg)
29+
print("Bot>", reply)
30+
31+
# vim:expandtab

rivescript/__init__.py

Lines changed: 39 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
#!/usr/bin/env python
22
# pyRiveScript - A RiveScript interpreter written in Python.
33

4+
# Python 3 compat
5+
from __future__ import print_function
6+
47
__author__ = 'Noah Petherbridge'
58
__copyright__ = 'Copyright 2013, Noah Petherbridge'
69
__credits__ = [
@@ -15,6 +18,7 @@
1518
__all__ = ['rivescript']
1619
__version__ = '1.05'
1720

21+
import sys
1822
import os
1923
import glob
2024
import re
@@ -24,7 +28,7 @@
2428
import copy
2529
import codecs
2630

27-
from python import PyRiveObjects
31+
from rivescript.python import PyRiveObjects
2832

2933
# Common regular expressions.
3034
re_equals = re.compile('\s*=\s*')
@@ -33,7 +37,6 @@
3337
re_weight = re.compile('\{weight=(\d+)\}')
3438
re_inherit = re.compile('\{inherits=(\d+)\}')
3539
re_wilds = re.compile('[\s\*\#\_]+')
36-
re_rot13 = re.compile('<rot13sub>(.+?)<bus31tor>')
3740
re_nasties = re.compile('[^A-Za-z0-9 ]')
3841

3942
# Version of RiveScript we support.
@@ -93,7 +96,7 @@ def VERSION(self=None):
9396

9497
def _say(self, message):
9598
if self._debug:
96-
print "[RS]", message
99+
print("[RS]", message)
97100
if self._log:
98101
# Log it to the file.
99102
fh = open(self._log, 'a')
@@ -102,13 +105,13 @@ def _say(self, message):
102105

103106
def _warn(self, message, fname='', lineno=0):
104107
if self._debug:
105-
print "[RS::Warning]",
108+
print("[RS::Warning]"),
106109
else:
107-
print "[RS]",
110+
print("[RS]"),
108111
if len(fname) and lineno > 0:
109-
print message, "at", fname, "line", lineno
112+
print(message, "at", fname, "line", lineno)
110113
else:
111-
print message
114+
print(message)
112115

113116
############################################################################
114117
# Loading and Parsing Methods #
@@ -877,7 +880,7 @@ def by_length(word1, word2):
877880
# Sort them.
878881
output = []
879882
for count in sorted(track.keys(), reverse=True):
880-
sort = sorted(track[count], cmp=by_length)
883+
sort = sorted(track[count], key=len, reverse=True)
881884
output.extend(sort)
882885

883886
self._sorted["lists"][name] = output
@@ -1129,8 +1132,8 @@ def reply(self, user, msg):
11291132
def _format_message(self, msg):
11301133
"""Format a user's message for safe processing."""
11311134

1132-
# Make sure the string is Unicode.
1133-
if isinstance(msg, str):
1135+
# Make sure the string is Unicode for Python 2.
1136+
if sys.version_info[0] < 3 and isinstance(msg, str):
11341137
msg = msg.decode('utf8')
11351138

11361139
# Lowercase it.
@@ -1440,19 +1443,29 @@ def _substitute(self, msg, list):
14401443
else:
14411444
subs = self._person
14421445

1446+
# Make placeholders each time we substitute something.
1447+
ph = []
1448+
i = 0
1449+
14431450
for pattern in self._sorted["lists"][list]:
1444-
result = "<rot13sub>" + self._rot13(subs[pattern]) + "<bus31tor>"
1451+
result = subs[pattern]
1452+
1453+
# Make a placeholder.
1454+
ph.append(result)
1455+
placeholder = "\x00%d\x00" % i
1456+
i += 1
1457+
14451458
qm = re.escape(pattern)
14461459
msg = re.sub(r'^' + qm + "$", result, msg)
14471460
msg = re.sub(r'^' + qm + r'(\W+)', result+r'\1', msg)
14481461
msg = re.sub(r'(\W+)' + qm + r'(\W+)', r'\1'+result+r'\2', msg)
14491462
msg = re.sub(r'(\W+)' + qm + r'$', r'\1'+result, msg)
14501463

1451-
placeholders = re.findall(re_rot13, msg)
1464+
placeholders = re.findall(r'\x00(\d+)\x00', msg)
14521465
for match in placeholders:
1453-
rot13 = match
1454-
decoded = self._rot13(match)
1455-
msg = re.sub('<rot13sub>' + re.escape(rot13) + '<bus31tor>', decoded, msg)
1466+
i = match
1467+
result = ph[i]
1468+
msg = re.sub(r'\x00' + i + r'\x00', result, msg)
14561469

14571470
# Strip & return.
14581471
return msg.strip()
@@ -1917,13 +1930,6 @@ def _word_count(self, trigger, all=False):
19171930

19181931
return wc
19191932

1920-
def _rot13(self, n):
1921-
"""Encode and decode a string into ROT13."""
1922-
trans = string.maketrans(
1923-
"ABCDEFGHIJKLMabcdefghijklmNOPQRSTUVWXYZnopqrstuvwxyz",
1924-
"NOPQRSTUVWXYZnopqrstuvwxyzABCDEFGHIJKLMabcdefghijklm")
1925-
return string.translate(str(n), trans)
1926-
19271933
def _strip_nasties(self, s):
19281934
"""Formats a string for ASCII regex matching."""
19291935
s = re.sub(re_nasties, '', s)
@@ -1933,30 +1939,30 @@ def _dump(self):
19331939
"""For debugging, dump the entire data structure."""
19341940
pp = pprint.PrettyPrinter(indent=4)
19351941

1936-
print "=== Variables ==="
1937-
print "-- Globals --"
1942+
print("=== Variables ===")
1943+
print("-- Globals --")
19381944
pp.pprint(self._gvars)
1939-
print "-- Bot vars --"
1945+
print("-- Bot vars --")
19401946
pp.pprint(self._bvars)
1941-
print "-- Substitutions --"
1947+
print("-- Substitutions --")
19421948
pp.pprint(self._subs)
1943-
print "-- Person Substitutions --"
1949+
print("-- Person Substitutions --")
19441950
pp.pprint(self._person)
1945-
print "-- Arrays --"
1951+
print("-- Arrays --")
19461952
pp.pprint(self._arrays)
19471953

1948-
print "=== Topic Structure ==="
1954+
print("=== Topic Structure ===")
19491955
pp.pprint(self._topics)
1950-
print "=== %Previous Structure ==="
1956+
print("=== %Previous Structure ===")
19511957
pp.pprint(self._thats)
19521958

1953-
print "=== Includes ==="
1959+
print("=== Includes ===")
19541960
pp.pprint(self._includes)
19551961

1956-
print "=== Inherits ==="
1962+
print("=== Inherits ===")
19571963
pp.pprint(self._lineage)
19581964

1959-
print "=== Sort Buffer ==="
1965+
print("=== Sort Buffer ===")
19601966
pp.pprint(self._sorted)
19611967

19621968
################################################################################

rivescript/python.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
#!/usr/bin/env python
22

3+
# Python3 compat
4+
from __future__ import print_function
5+
36
"""Python object macro support for RiveScript.
47
58
This class provides built-in support for your RiveScript documents to include
@@ -34,11 +37,14 @@ def load(self, name, code):
3437
for line in code:
3538
source = source + "\t" + line + "\n"
3639

40+
source += "self._objects[name] = RSOBJ\n"
41+
3742
try:
38-
exec source
39-
self._objects[name] = RSOBJ
40-
except:
41-
print "Failed to load code from object " + name
43+
exec(source)
44+
#self._objects[name] = RSOBJ
45+
except Exception as e:
46+
print("Failed to load code from object", name)
47+
print("The error given was: ", e)
4248

4349
def call(self, rs, name, fields):
4450
"""Invoke a previously loaded object."""
@@ -49,6 +55,7 @@ def call(self, rs, name, fields):
4955
reply = func(rs, fields)
5056
if reply == None:
5157
reply = ''
52-
except:
58+
except Exception as e:
59+
print("Error executing Python object:", e)
5360
reply = '[ERR: Error when executing Python object]'
54-
return reply
61+
return str(reply)

0 commit comments

Comments
 (0)