Author: Armin Rigo <[email protected]>
Branch:
Changeset: r90638:46167d4097c8
Date: 2017-03-12 18:57 +0100
http://bitbucket.org/pypy/pypy/changeset/46167d4097c8/
Log: Issue #2492
Modernize the interface of _minimal_curses. Also moves the logic to
avoid any ``#include <term.h>`` from anywhere except a single small
C file.
diff --git a/pypy/module/_minimal_curses/fficurses.py
b/pypy/module/_minimal_curses/fficurses.py
--- a/pypy/module/_minimal_curses/fficurses.py
+++ b/pypy/module/_minimal_curses/fficurses.py
@@ -1,11 +1,8 @@
-""" The ffi for rpython, need to be imported for side effects
+""" The ffi for rpython
"""
from rpython.rtyper.lltypesystem import rffi
-from rpython.rtyper.lltypesystem import lltype
from rpython.rtyper.tool import rffi_platform
-from rpython.rtyper.extfunc import register_external
-from pypy.module._minimal_curses import interp_curses
from rpython.translator.tool.cbuild import ExternalCompilationInfo
# We cannot trust ncurses5-config, it's broken in various ways in
@@ -58,86 +55,73 @@
eci = guess_eci()
-INT = rffi.INT
-INTP = lltype.Ptr(lltype.Array(INT, hints={'nolength':True}))
-c_setupterm = rffi.llexternal('setupterm', [rffi.CCHARP, INT, INTP], INT,
- compilation_info=eci)
-c_tigetstr = rffi.llexternal('tigetstr', [rffi.CCHARP], rffi.CCHARP,
- compilation_info=eci)
-c_tparm = rffi.llexternal('tparm', [rffi.CCHARP, INT, INT, INT, INT, INT,
- INT, INT, INT, INT], rffi.CCHARP,
- compilation_info=eci)
+# We should not use this 'eci' directly because it causes the #include
+# of term.h to appear in all generated C sources, and term.h contains a
+# poisonous quantity of #defines for common lower-case names like
+# 'buttons' or 'lines' (!!!). It is basically dangerous to include
+# term.h in any C source file that may contain unrelated source code.
-ERR = rffi.CConstant('ERR', lltype.Signed)
-OK = rffi.CConstant('OK', lltype.Signed)
+include_lines = '\n'.join(['#include <%s>' % _incl for _incl in eci.includes])
+eci = eci.copy_without('includes')
-def curses_setupterm(term, fd):
- intp = lltype.malloc(INTP.TO, 1, flavor='raw')
- err = rffi.cast(lltype.Signed, c_setupterm(term, fd, intp))
- try:
- if err == ERR:
- errret = rffi.cast(lltype.Signed, intp[0])
- if errret == 0:
- msg = "setupterm: could not find terminal"
- elif errret == -1:
- msg = "setupterm: could not find terminfo database"
- else:
- msg = "setupterm: unknown error"
- raise interp_curses.curses_error(msg)
- interp_curses.module_info.setupterm_called = True
- finally:
- lltype.free(intp, flavor='raw')
-def curses_setupterm_null_llimpl(fd):
- curses_setupterm(lltype.nullptr(rffi.CCHARP.TO), fd)
+eci = eci.merge(ExternalCompilationInfo(
+ post_include_bits=[
+ "RPY_EXTERN char *rpy_curses_setupterm(char *, int);\n"
+ "RPY_EXTERN char *rpy_curses_tigetstr(char *);\n"
+ "RPY_EXTERN char *rpy_curses_tparm(char *, int, int, int, int,"
+ " int, int, int, int, int);"
+ ],
+ separate_module_sources=["""
-def curses_setupterm_llimpl(term, fd):
- ll_s = rffi.str2charp(term)
- try:
- curses_setupterm(ll_s, fd)
- finally:
- rffi.free_charp(ll_s)
+%(include_lines)s
-register_external(interp_curses._curses_setupterm_null,
- [int], llimpl=curses_setupterm_null_llimpl,
- export_name='_curses.setupterm_null')
-register_external(interp_curses._curses_setupterm,
- [str, int], llimpl=curses_setupterm_llimpl,
- export_name='_curses.setupterm')
+RPY_EXTERN
+char *rpy_curses_setupterm(char *term, int fd)
+{
+ int errret = -42;
+ if (setupterm(term, fd, &errret) == ERR) {
+ switch (errret) {
+ case 0:
+ return "setupterm: could not find terminal";
+ case -1:
+ return "setupterm: could not find terminfo database";
+ default:
+ return "setupterm: unknown error";
+ }
+ }
+ return NULL;
+}
-def check_setup_invoked():
- if not interp_curses.module_info.setupterm_called:
- raise interp_curses.curses_error("must call (at least) setupterm()
first")
+RPY_EXTERN
+char *rpy_curses_tigetstr(char *capname)
+{
+ char *res = tigetstr(capname);
+ if (res == (char *)-1)
+ res = NULL;
+ return res;
+}
-def tigetstr_llimpl(cap):
- check_setup_invoked()
- ll_cap = rffi.str2charp(cap)
- try:
- ll_res = c_tigetstr(ll_cap)
- num = lltype.cast_ptr_to_int(ll_res)
- if num == 0 or num == -1:
- raise interp_curses.TermError()
- res = rffi.charp2str(ll_res)
- return res
- finally:
- rffi.free_charp(ll_cap)
+RPY_EXTERN
+char *rpy_curses_tparm(char *str, int x0, int x1, int x2, int x3,
+ int x4, int x5, int x6, int x7, int x8)
+{
+ return tparm(str, x0, x1, x2, x3, x4, x5, x6, x7, x8);
+}
-register_external(interp_curses._curses_tigetstr, [str], str,
- export_name='_curses.tigetstr', llimpl=tigetstr_llimpl)
+""" % globals()]))
-def tparm_llimpl(s, args):
- check_setup_invoked()
- l = [0, 0, 0, 0, 0, 0, 0, 0, 0]
- for i in range(min(len(args), 9)):
- l[i] = args[i]
- ll_s = rffi.str2charp(s)
- # XXX nasty trick stolen from CPython
- ll_res = c_tparm(ll_s, l[0], l[1], l[2], l[3], l[4], l[5], l[6],
- l[7], l[8])
- rffi.free_charp(ll_s)
- res = rffi.charp2str(ll_res)
- return res
-register_external(interp_curses._curses_tparm, [str, [int]], str,
- export_name='_curses.tparm', llimpl=tparm_llimpl)
+rpy_curses_setupterm = rffi.llexternal(
+ "rpy_curses_setupterm", [rffi.CCHARP, rffi.INT], rffi.CCHARP,
+ compilation_info=eci)
+rpy_curses_tigetstr = rffi.llexternal(
+ "rpy_curses_tigetstr", [rffi.CCHARP], rffi.CCHARP,
+ compilation_info=eci)
+
+rpy_curses_tparm = rffi.llexternal(
+ "rpy_curses_tparm", [rffi.CCHARP, rffi.INT, rffi.INT, rffi.INT, rffi.INT,
+ rffi.INT, rffi.INT, rffi.INT, rffi.INT, rffi.INT],
+ rffi.CCHARP,
+ compilation_info=eci)
diff --git a/pypy/module/_minimal_curses/interp_curses.py
b/pypy/module/_minimal_curses/interp_curses.py
--- a/pypy/module/_minimal_curses/interp_curses.py
+++ b/pypy/module/_minimal_curses/interp_curses.py
@@ -1,44 +1,24 @@
-
from pypy.interpreter.gateway import unwrap_spec
from pypy.interpreter.error import OperationError
-from pypy.module._minimal_curses import _curses
+from pypy.module._minimal_curses import fficurses
+from rpython.rtyper.lltypesystem import lltype, rffi
+
class ModuleInfo:
- def __init__(self):
+ def __init__(self, space):
self.setupterm_called = False
-module_info = ModuleInfo()
+def check_setup_invoked(space):
+ if not space.fromcache(ModuleInfo).setupterm_called:
+ raise curses_error(space, "must call (at least) setupterm() first")
-class curses_error(Exception):
- def __init__(self, msg):
- self.msg = msg
-from rpython.annotator.classdesc import FORCE_ATTRIBUTES_INTO_CLASSES
-from rpython.annotator.model import SomeString
-
-# this is necessary due to annmixlevel
-FORCE_ATTRIBUTES_INTO_CLASSES[curses_error] = {'msg': SomeString()}
-
-def convert_error(space, error):
- msg = error.msg
+def curses_error(space, errmsg):
w_module = space.getbuiltinmodule('_minimal_curses')
w_exception_class = space.getattr(w_module, space.newtext('error'))
- w_exception = space.call_function(w_exception_class, space.newtext(msg))
+ w_exception = space.call_function(w_exception_class, space.newtext(errmsg))
return OperationError(w_exception_class, w_exception)
-def _curses_setupterm_null(fd):
- # NOT_RPYTHON
- try:
- _curses.setupterm(None, fd)
- except _curses.error as e:
- raise curses_error(e.args[0])
-
-def _curses_setupterm(termname, fd):
- # NOT_RPYTHON
- try:
- _curses.setupterm(termname, fd)
- except _curses.error as e:
- raise curses_error(e.args[0])
@unwrap_spec(fd=int)
def setupterm(space, w_termname=None, fd=-1):
@@ -47,48 +27,47 @@
space.newtext('stdout'))
fd = space.int_w(space.call_function(space.getattr(w_stdout,
space.newtext('fileno'))))
- try:
- if space.is_none(w_termname):
- _curses_setupterm_null(fd)
- else:
- _curses_setupterm(space.text_w(w_termname), fd)
- except curses_error as e:
- raise convert_error(space, e)
+ if space.is_none(w_termname):
+ termname = None
+ else:
+ termname = space.text_w(w_termname)
-class TermError(Exception):
- pass
+ with rffi.scoped_str2charp(termname) as ll_term:
+ fd = rffi.cast(rffi.INT, fd)
+ ll_errmsg = fficurses.rpy_curses_setupterm(ll_term, fd)
+ if ll_errmsg:
+ raise curses_error(space, rffi.charp2str(ll_errmsg))
-def _curses_tigetstr(capname):
- # NOT_RPYTHON
- try:
- res = _curses.tigetstr(capname)
- except _curses.error as e:
- raise curses_error(e.args[0])
- if res is None:
- raise TermError
- return res
-
-def _curses_tparm(s, args):
- # NOT_RPYTHON
- try:
- return _curses.tparm(s, *args)
- except _curses.error as e:
- raise curses_error(e.args[0])
+ space.fromcache(ModuleInfo).setupterm_called = True
@unwrap_spec(capname='text')
def tigetstr(space, capname):
- try:
- result = _curses_tigetstr(capname)
- except TermError:
- return space.w_None
- except curses_error as e:
- raise convert_error(space, e)
- return space.newbytes(result)
+ check_setup_invoked(space)
+ with rffi.scoped_str2charp(capname) as ll_capname:
+ ll_result = fficurses.rpy_curses_tigetstr(ll_capname)
+ if ll_result:
+ return space.newbytes(rffi.charp2str(ll_result))
+ else:
+ return space.w_None
@unwrap_spec(s='text')
def tparm(space, s, args_w):
+ check_setup_invoked(space)
args = [space.int_w(a) for a in args_w]
- try:
- return space.newbytes(_curses_tparm(s, args))
- except curses_error as e:
- raise convert_error(space, e)
+ # nasty trick stolen from CPython
+ x0 = args[0] if len(args) > 0 else 0
+ x1 = args[1] if len(args) > 1 else 0
+ x2 = args[2] if len(args) > 2 else 0
+ x3 = args[3] if len(args) > 3 else 0
+ x4 = args[4] if len(args) > 4 else 0
+ x5 = args[5] if len(args) > 5 else 0
+ x6 = args[6] if len(args) > 6 else 0
+ x7 = args[7] if len(args) > 7 else 0
+ x8 = args[8] if len(args) > 8 else 0
+ with rffi.scoped_str2charp(s) as ll_str:
+ ll_result = fficurses.rpy_curses_tparm(ll_str, x0, x1, x2, x3,
+ x4, x5, x6, x7, x8)
+ if ll_result:
+ return space.newbytes(rffi.charp2str(ll_result))
+ else:
+ raise curses_error(space, "tparm() returned NULL")
diff --git a/pypy/module/_minimal_curses/test/test_curses.py
b/pypy/module/_minimal_curses/test/test_curses.py
--- a/pypy/module/_minimal_curses/test/test_curses.py
+++ b/pypy/module/_minimal_curses/test/test_curses.py
@@ -76,19 +76,27 @@
"""
def test_csetupterm(self):
from rpython.translator.c.test.test_genc import compile
- from pypy.module._minimal_curses import interp_curses
+ from rpython.rtyper.lltypesystem import lltype, rffi
+ from pypy.module._minimal_curses import fficurses
+
def runs_setupterm():
- interp_curses._curses_setupterm_null(1)
+ null = lltype.nullptr(rffi.CCHARP.TO)
+ fficurses.rpy_curses_setupterm(null, 1)
fn = compile(runs_setupterm, [])
fn()
def test_ctgetstr(self):
from rpython.translator.c.test.test_genc import compile
- from pypy.module._minimal_curses import interp_curses
+ from rpython.rtyper.lltypesystem import lltype, rffi
+ from pypy.module._minimal_curses import fficurses
+
def runs_ctgetstr():
- interp_curses._curses_setupterm("xterm", 1)
- return interp_curses._curses_tigetstr('cup')
+ with rffi.scoped_str2charp("xterm") as ll_term:
+ fficurses.rpy_curses_setupterm(ll_term, 1)
+ with rffi.scoped_str2charp("cup") as ll_capname:
+ ll = fficurses.rpy_curses_tigetstr(ll_capname)
+ return rffi.charp2str(ll)
fn = compile(runs_ctgetstr, [])
res = fn()
@@ -96,11 +104,16 @@
def test_ctparm(self):
from rpython.translator.c.test.test_genc import compile
- from pypy.module._minimal_curses import interp_curses
+ from rpython.rtyper.lltypesystem import lltype, rffi
+ from pypy.module._minimal_curses import fficurses
+
def runs_tparm():
- interp_curses._curses_setupterm("xterm", 1)
- cup = interp_curses._curses_tigetstr('cup')
- return interp_curses._curses_tparm(cup, [5, 3])
+ with rffi.scoped_str2charp("xterm") as ll_term:
+ fficurses.rpy_curses_setupterm(ll_term, 1)
+ with rffi.scoped_str2charp("cup") as ll_capname:
+ cup = fficurses.rpy_curses_tigetstr(ll_capname)
+ res = fficurses.rpy_curses_tparm(cup, 5, 3, 0, 0, 0, 0, 0, 0,
0)
+ return rffi.charp2str(res)
fn = compile(runs_tparm, [])
res = fn()
diff --git a/rpython/translator/tool/cbuild.py
b/rpython/translator/tool/cbuild.py
--- a/rpython/translator/tool/cbuild.py
+++ b/rpython/translator/tool/cbuild.py
@@ -334,3 +334,9 @@
d['separate_module_files'] = ()
d['separate_module_sources'] = ()
return ExternalCompilationInfo(**d)
+
+ def copy_without(self, *names):
+ d = self._copy_attributes()
+ for name in names:
+ del d[name]
+ return ExternalCompilationInfo(**d)
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit