diff --git a/README.md b/README.md index 8aed3d8..a1d99f0 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,6 @@ prolog.consult("knowledge_base.pl") ### Foreign Functions ```python -from __future__ import print_function from pyswip import Prolog, registerForeign def hello(t): @@ -77,7 +76,6 @@ print(list(prolog.query("father(michael,X), hello(X)"))) ### Pythonic interface (Experimental) ```python -from __future__ import print_function from pyswip import Functor, Variable, Query, call assertz = Functor("assertz", 1) diff --git a/examples/coins/coins.py b/examples/coins/coins.py index 670fac7..a1ae124 100644 --- a/examples/coins/coins.py +++ b/examples/coins/coins.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - # pyswip -- Python SWI-Prolog bridge # Copyright (c) 2007-2018 Yüce Tekol # @@ -23,27 +21,21 @@ # 100 coins must sum to $5.00 -from __future__ import print_function from pyswip.prolog import Prolog -try: - input = raw_input -except NameError: - pass - def main(): prolog = Prolog() - prolog.consult("coins.pl") + prolog.consult("coins.pl", relative_to=__file__) count = int(input("How many coins (default: 100)? ") or 100) total = int(input("What should be the total (default: 500)? ") or 500) for i, soln in enumerate(prolog.query("coins(S, %d, %d)." % (count, total))): S = zip(soln["S"], [1, 5, 10, 50, 100]) print(i, end=" ") for c, v in S: - print("%dx%d" % (c, v), end=" ") + print(f"{c}x{v}", end=" ") print() - list(prolog.query("coins(S, %d, %d)." % (count, total))) + list(prolog.query(f"coins(S, {count}, {total}).")) if __name__ == "__main__": diff --git a/examples/coins/coins_new.py b/examples/coins/coins_new.py index 6f3b403..e12f08b 100644 --- a/examples/coins/coins_new.py +++ b/examples/coins/coins_new.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - # pyswip -- Python SWI-Prolog bridge # Copyright (c) 2007-2018 Yüce Tekol # @@ -23,19 +21,12 @@ # 100 coins must sum to $5.00 -from __future__ import print_function from pyswip import Prolog, Functor, Variable, Query -try: - input = raw_input -except NameError: - pass - - def main(): prolog = Prolog() - prolog.consult("coins.pl") + prolog.consult("coins.pl", relative_to=__file__) count = int(input("How many coins (default: 100)? ") or 100) total = int(input("What should be the total (default: 500)? ") or 500) coins = Functor("coins", 3) @@ -47,7 +38,7 @@ def main(): s = zip(S.value, [1, 5, 10, 50, 100]) print(i, end=" ") for c, v in s: - print("%dx%d" % (c, v), end=" ") + print(f"{c}x{v}", end=" ") print() i += 1 q.closeQuery() diff --git a/examples/draughts/puzzle1.py b/examples/draughts/puzzle1.py index 6cc0614..e44bfcb 100644 --- a/examples/draughts/puzzle1.py +++ b/examples/draughts/puzzle1.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - # pyswip -- Python SWI-Prolog bridge # Copyright (c) 2007-2018 Yüce Tekol # @@ -35,19 +33,12 @@ # are four guards watching each wall. How can they be rearranged such # that there are five watching each wall?" -from __future__ import print_function from pyswip.prolog import Prolog -try: - input = raw_input -except NameError: - pass - - def main(): prolog = Prolog() - prolog.consult("puzzle1.pl") + prolog.consult("puzzle1.pl", relative_to=__file__) for soln in prolog.query("solve(B)."): B = soln["B"] diff --git a/examples/hanoi/hanoi.py b/examples/hanoi/hanoi.py index 93af1b1..528a260 100644 --- a/examples/hanoi/hanoi.py +++ b/examples/hanoi/hanoi.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - # pyswip -- Python SWI-Prolog bridge # Copyright (c) 2007-2018 Yüce Tekol # @@ -21,7 +19,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from __future__ import print_function from collections import deque from pyswip.prolog import Prolog @@ -78,15 +75,13 @@ def draw(self): def main(): - N = 3 - INTERACTIVITY = True - + n = 3 prolog = Prolog() - tower = Tower(N, INTERACTIVITY) + tower = Tower(n, True) notifier = Notifier(tower.move) registerForeign(notifier.notify) - prolog.consult("hanoi.pl") - list(prolog.query("hanoi(%d)" % N)) + prolog.consult("hanoi.pl", relative_to=__file__) + list(prolog.query("hanoi(%d)" % n)) if __name__ == "__main__": diff --git a/examples/hanoi/hanoi_simple.py b/examples/hanoi/hanoi_simple.py index 9b73fde..0fb72ca 100644 --- a/examples/hanoi/hanoi_simple.py +++ b/examples/hanoi/hanoi_simple.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - # pyswip -- Python SWI-Prolog bridge # Copyright (c) 2007-2018 Yüce Tekol # @@ -21,7 +19,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from __future__ import print_function from pyswip.prolog import Prolog from pyswip.easy import registerForeign @@ -36,8 +33,8 @@ def notify(t): prolog = Prolog() registerForeign(notify) - prolog.consult("hanoi.pl") - list(prolog.query("hanoi(%d)" % N)) + prolog.consult("hanoi.pl", relative_to=__file__) + list(prolog.query(f"hanoi({N})")) if __name__ == "__main__": diff --git a/examples/sendmoremoney/money.py b/examples/sendmoremoney/money.py index f70f661..21a4d2d 100644 --- a/examples/sendmoremoney/money.py +++ b/examples/sendmoremoney/money.py @@ -1,7 +1,5 @@ -# -*- coding: utf-8 -*- - # pyswip -- Python SWI-Prolog bridge -# Copyright (c) 2007-2018 Yüce Tekol +# Copyright (c) 2007-2024 Yüce Tekol # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -29,12 +27,11 @@ # So, what should be the values of S, E, N, D, M, O, R, Y # if they are all distinct digits. -from __future__ import print_function from pyswip import Prolog -letters = "S E N D M O R Y".split() +letters = list("SENDMORY") prolog = Prolog() -prolog.consult("money.pl") +prolog.consult("money.pl", relative_to=__file__) for result in prolog.query("sendmore(X)"): r = result["X"] for i, letter in enumerate(letters): diff --git a/examples/sendmoremoney/money_new.py b/examples/sendmoremoney/money_new.py index 8b63001..fcbc7a4 100644 --- a/examples/sendmoremoney/money_new.py +++ b/examples/sendmoremoney/money_new.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - # pyswip -- Python SWI-Prolog bridge # Copyright (c) 2007-2018 Yüce Tekol # @@ -29,15 +27,14 @@ # So, what should be the values of S, E, N, D, M, O, R, Y # if they are all distinct digits. -from __future__ import print_function from pyswip import Prolog, Functor, Variable, call def main(): - letters = "S E N D M O R Y".split() + letters = list("SENDMORY") prolog = Prolog() sendmore = Functor("sendmore") - prolog.consult("money.pl") + prolog.consult("money.pl", relative_to=__file__) X = Variable() call(sendmore(X)) diff --git a/src/pyswip/__init__.py b/src/pyswip/__init__.py index fd3d872..520d29f 100644 --- a/src/pyswip/__init__.py +++ b/src/pyswip/__init__.py @@ -1,8 +1,5 @@ # -*- coding: utf-8 -*- - - -# pyswip -- Python SWI-Prolog bridge -# Copyright (c) 2007-2018 Yüce Tekol +# Copyright (c) 2007-2024 Yüce Tekol and PySwip Contributors # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -22,8 +19,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. - -# PySwip version __VERSION__ = "0.3.1" from pyswip.prolog import Prolog as Prolog diff --git a/src/pyswip/core.py b/src/pyswip/core.py index aa364cd..aed0550 100644 --- a/src/pyswip/core.py +++ b/src/pyswip/core.py @@ -1,8 +1,4 @@ -# -*- coding: utf-8 -*- - - -# pyswip -- Python SWI-Prolog bridge -# Copyright (c) 2007-2018 Yüce Tekol +# Copyright (c) 2007-2024 Yüce Tekol and PySwip Contributors # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -22,8 +18,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from __future__ import print_function - import atexit import glob import os diff --git a/src/pyswip/easy.py b/src/pyswip/easy.py index e751d73..04a23bd 100644 --- a/src/pyswip/easy.py +++ b/src/pyswip/easy.py @@ -1,8 +1,4 @@ -# -*- coding: utf-8 -*- - - -# pyswip.easy -- PySwip helper functions -# Copyright (c) 2007-2018 Yüce Tekol +# Copyright (c) 2007-2024 Yüce Tekol and PySwip Contributors # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -22,18 +18,10 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. - from pyswip.core import * -# For backwards compability with Python 2 64bit -if sys.version_info < (3,): - integer_types = ( - int, - long, - ) -else: - integer_types = (int,) +integer_types = (int,) class InvalidTypeError(TypeError): @@ -146,17 +134,8 @@ def __hash__(self): return self.handle -# support unicode also in python 2 -try: - isinstance("", basestring) - - def isstr(s): - return isinstance(s, basestring) - -except NameError: - - def isstr(s): - return isinstance(s, str) +def isstr(s): + return isinstance(s, str) class Variable(object): diff --git a/src/pyswip/prolog.py b/src/pyswip/prolog.py index 3aaa117..b695b79 100644 --- a/src/pyswip/prolog.py +++ b/src/pyswip/prolog.py @@ -1,8 +1,4 @@ -# -*- coding: utf-8 -*- - - -# prolog.py -- Prolog class -# Copyright (c) 2007-2012 Yüce Tekol +# Copyright (c) 2007-2024 Yüce Tekol and PySwip Contributors # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -22,8 +18,34 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. - -from pyswip.core import * +from typing import Union +from pathlib import Path + +from pyswip.utils import resolve_path +from pyswip.core import ( + SWI_HOME_DIR, + PL_STRING, + REP_UTF8, + PL_Q_NODEBUG, + PL_Q_CATCH_EXCEPTION, + PL_Q_NORMAL, + PL_initialise, + PL_open_foreign_frame, + PL_new_term_ref, + PL_chars_to_term, + PL_call, + PL_discard_foreign_frame, + PL_new_term_refs, + PL_put_chars, + PL_predicate, + PL_open_query, + PL_next_solution, + PL_copy_term_ref, + PL_exception, + PL_cut_query, + PL_thread_self, + PL_thread_attach_engine, +) class PrologError(Exception): @@ -180,8 +202,11 @@ def retractall(cls, term, catcherrors=False): next(cls.query(term.join(["retractall((", "))."]), catcherrors=catcherrors)) @classmethod - def consult(cls, filename, catcherrors=False): - next(cls.query(filename.join(["consult('", "')"]), catcherrors=catcherrors)) + def consult( + cls, filename: str, *, catcherrors=False, relative_to: Union[str, Path] = "" + ): + path = resolve_path(filename, relative_to) + next(cls.query(str(path).join(["consult('", "')"]), catcherrors=catcherrors)) @classmethod def query(cls, query, maxresult=-1, catcherrors=True, normalize=True): diff --git a/src/pyswip/utils.py b/src/pyswip/utils.py new file mode 100644 index 0000000..006cda3 --- /dev/null +++ b/src/pyswip/utils.py @@ -0,0 +1,37 @@ +# Copyright (c) 2007-2024 Yüce Tekol and PySwip Contributors +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from typing import Union +from pathlib import Path + + +def resolve_path(filename: str, relative_to: Union[str, Path] = "") -> Path: + if not relative_to: + return Path(filename) + relative_to = Path(relative_to) + if not relative_to.exists(): + raise FileNotFoundError(None, "Relative path does not exist", str(relative_to)) + if relative_to.is_symlink(): + raise ValueError("Symbolic links are not supported") + if relative_to.is_dir(): + return relative_to / filename + elif relative_to.is_file(): + return relative_to.parent / filename + raise ValueError("relative_to must be either a filename or a directory") diff --git a/tests/test_issues.py b/tests/test_issues.py index 6ca84a1..ba4c437 100644 --- a/tests/test_issues.py +++ b/tests/test_issues.py @@ -28,9 +28,6 @@ import subprocess import sys import unittest -import os - -current_dir = os.path.dirname(os.path.abspath(__file__)) class TestIssues(unittest.TestCase): @@ -250,8 +247,7 @@ def test_issue_62(self): from pyswip import Prolog prolog = Prolog() - path = os.path.join(current_dir, "test_unicode.pl") - prolog.consult(path, catcherrors=True) + prolog.consult("test_unicode.pl", catcherrors=True, relative_to=__file__) atoms = list(prolog.query("unicode_atom(B).")) self.assertEqual(len(atoms), 3, "Query should return exactly three atoms") @@ -275,8 +271,7 @@ def test_functor_return(self): import pyswip.prolog as pl p = pl.Prolog() - path = os.path.join(current_dir, "test_functor_return.pl") - p.consult(path, catcherrors=True) + p.consult("test_functor_return.pl", catcherrors=True, relative_to=__file__) query = "sentence(Parse_tree, [the,bat,eats,a,cat], [])" expectedTree = "s(np(d(the), n(bat)), vp(v(eats), np(d(a), n(cat))))" diff --git a/tests/test_prolog.py b/tests/test_prolog.py index 59fff35..a870789 100644 --- a/tests/test_prolog.py +++ b/tests/test_prolog.py @@ -30,7 +30,6 @@ import unittest import pyswip.prolog as pl # This implicitly tests library loading code -from tests.test_issues import current_dir class TestProlog(unittest.TestCase): @@ -112,6 +111,7 @@ def test_prolog_read_file(self): """ See: https://github.com/yuce/pyswip/issues/10 """ + current_dir = os.path.dirname(os.path.abspath(__file__)) prolog = pl.Prolog() path = os.path.join(current_dir, "test_read.pl") prolog.consult(path) diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..cfca329 --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,51 @@ +# pyswip -- Python SWI-Prolog bridge +# Copyright (c) 2007-2024 Yüce Tekol and PySwip +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import os +import unittest +import tempfile +from pathlib import Path + +from pyswip.utils import resolve_path + + +class UtilsTestCase(unittest.TestCase): + def test_resolve_path_given_file(self): + filename = "test_read.pl" + path = resolve_path(filename) + self.assertEqual(Path(filename), path) + + def test_resolve_path_given_dir(self): + filename = "test_read.pl" + path = resolve_path(filename, __file__) + current_dir = Path(__file__).parent.absolute() + self.assertEqual(current_dir / filename, path) + + def test_resolve_path_symbolic_link(self): + current_dir = Path(__file__).parent.absolute() + path = current_dir / "test_read.pl" + temp_dir = tempfile.TemporaryDirectory("pyswip") + try: + symlink = Path(temp_dir.name) / "symlinked" + os.symlink(path, symlink) + self.assertRaises(ValueError, lambda: resolve_path("test_read.pl", symlink)) + finally: + temp_dir.cleanup()