Migrate from switch to native Python module.

This commit is contained in:
Julien Palard 2022-11-01 14:18:39 +01:00
parent 102ecca8ec
commit 42e7bdd22b
Signed by: mdk
GPG Key ID: 0EFC1AC1006886F8
10 changed files with 421 additions and 184 deletions

4
.gitignore vendored
View File

@ -1,4 +1,6 @@
*.o
*.so
parser
*.egg-info/
.venv/
.envrc

1
MANIFEST.in Normal file
View File

@ -0,0 +1 @@
include src/*.h

View File

@ -2,30 +2,6 @@
## Makefile for vt100
##
## Made by julien palard
## Login <vt100@mandark.fr>
##
## Copyright (c) 2016 Julien Palard.
## All rights reserved.
##
## Redistribution and use in source and binary forms, with or without
## modification, are permitted provided that the following conditions
## are met:
## 1. Redistributions of source code must retain the above copyright
## notice, this list of conditions and the following disclaimer.
## 2. Redistributions in binary form must reproduce the above copyright
## notice, this list of conditions and the following disclaimer in the
## documentation and/or other materials provided with the distribution.
##
## THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
## IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
## OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
## IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
## INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
## NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
## THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
##
NAME = vt100
@ -54,20 +30,13 @@ $(NAME): $(OBJ)
test: $(OBJ_TEST)
$(CC) $(OBJ_TEST) -L . -l$(NAME) -o test
python_module:
swig -python -threads *.i
all:
@make $(NAME)
.c.o:
$(CC) -D $(DEFINE) -c $(CFLAGS) $< -o $(<:.c=.o)
clean_python_module:
$(RM) *.pyc *.so hl_vt100_wrap.c hl_vt100.py
$(RM) -r build
clean: clean_python_module
clean:
$(RM) $(LINKERNAME) test src/*~ *~ src/\#*\# src/*.o \#*\# *.o *core
re: clean all

125
README.md
View File

@ -10,63 +10,91 @@ want with it, like interfacing over TCP, HTTP, automatically testing
your implementation `malloc` against `top` while running `top` in the
headless terminal, whatever pleases you.
For copyright information, please see the file COPYRIGHT in this
For copyright information, please see the file LICENSE in this
directory or in the files of the source tree.
# INSTALL
## Python module
You'll need `swig`, so `apt-get install swig` or whatever works for you.
pip install hl-vt100
Run:
$ make python_module && su -c 'python setup.py install'
## Python module from source
# Usage using the python wrapper (same methods in C)
The simpliest way is just to run `pip install .` from within the repo,
but if you want build artifacts, you can build one in an isolated
environment using:
pip install build
python -m build
Or just create an `sdist` the quick way:
n
python setup.py sdist
In both case it will provide a build artifact in the `dist/` directory
that you can also `pip install`.
# Usage using the Python wrapper (same methods in C)
```python
import hl_vt100
def dump(vt100):
print("╭" + "─" * vt100.width + "╮")
for line in vt100.getlines():
print(f"│{line:{vt100.width}}│")
print("╰" + "─" * vt100.width + "╯")
def main():
vt100 = hl_vt100.vt100_headless()
vt100.changed_callback = lambda: dump(vt100)
vt100.fork('top', ['top'])
vt100.main_loop()
if __name__ == '__main__':
main()
```
>>> import hl_vt100
>>> import threading
>>> class Top:
... def __init__(self):
... self.vt100 = hl_vt100.vt100_headless()
... self.vt100.fork('top', ['top'])
...
... def __call__(self):
... self.vt100.main_loop()
...
... def __str__(self):
... return "\n".join(self.vt100.getlines())
...
>>> top = Top()
>>> thread = threading.Thread(target=top)
>>> thread.start()
>>> print(top)
top - 09:42:43 up 332 days, 23:42, 14 users, load average: 3.08, 3.41, 3.52
KiB Mem: 16443168 total, 15865172 used, 577996 free, 1141216 buffers
KiB Swap: 999420 total, 669960 used, 329460 free. 6097952 cached Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
24237 nobody 20 0 1041352 348040 39116 R 78.8 2.1 142:18.54 redacted
24078 nobody 20 0 1152024 382592 38948 R 25.8 2.3 355:44.85 for
24892 nobody 20 0 1182408 434560 40276 S 21.1 2.6 394:06.48 privacy
24472 nobody 20 0 1153196 380712 39000 S 20.3 2.3 410:51.64 ayah
23617 nobody 20 0 170048 100072 6376 S 19.5 0.6 8:36.66 python
23646 nobody 20 0 1095252 367128 39916 R 17.9 2.2 353:48.65 you
23505 nobody 20 0 1159452 377948 38920 S 17.2 2.3 364:29.40 wont
24757 nobody 20 0 943848 216084 40028 R 15.6 1.3 231:10.38 know
23894 nobody 20 0 885740 166600 38724 S 13.3 1.0 195:48.99 what
24689 nobody 20 0 901152 183240 39780 S 13.3 1.1 192:50.13 eats
24820 nobody 20 0 937712 232392 39496 S 13.3 1.4 215:25.32 my_cpu
3117 nobody 20 0 402568 351740 5912 S 3.1 2.1 0:03.72 python
3122 mandark 20 0 27052 3040 1184 R 3.1 0.0 0:00.34 top
23796 nobody 20 0 1069940 347252 39156 S 3.1 2.1 190:37.78 probably
23503 nobody 20 0 903592 268248 38360 S 1.6 1.6 75:54.32 somethign
5169 root 20 0 0 0 0 S 0.8 0.0 45:05.66 kworker/0:1
15324 root 20 0 63848 9768 1628 S 0.8 0.1 437:02.99 supervisord
16382 root 20 0 0 0 0 S 0.8 0.0 11:29.88 kworker/2:0
18278 root 20 0 0 0 0 S 0.8 0.0 184:02.23 kworker/1:0
# Usage using the C library
```c
#include <stdio.h>
#include <stdlib.h>
#include "hl_vt100.h"
void changed(struct vt100_headless *vt100)
{
const char **lines;
lines = vt100_headless_getlines(vt100);
for (unsigned int y = 0; y < vt100->term->height; ++y)
{
write(1, "|", 1);
write(1, lines[y], vt100->term->width);
write(1, "|\n", 2);
}
write(1, "\n", 1);
}
int main(int ac, char **av)
{
struct vt100_headless *vt100;
char *argv[] = {"top", NULL};
vt100 = new_vt100_headless();
vt100_headless_fork(vt100, argv[0], argv);
vt100->changed = changed;
vt100_headless_main_loop(vt100);
return EXIT_SUCCESS;
}
```
# Code overview
@ -126,7 +154,7 @@ lw_terminal_parser, lw_terminal_vt100, and hl_vt100 are three modules used to em
`lw_terminal_parser` parses terminal escape sequences, calling callbacks
when a sequence is sucessfully parsed, read `example/parse.c`.
Provides :
Provides:
* `struct lw_terminal *lw_terminal_parser_init(void);`
* `void lw_terminal_parser_destroy(struct lw_terminal* this);`
@ -140,7 +168,7 @@ Provides :
Hooks into a `lw_terminal_parser` and keep an in-memory state of the
screen of a vt100.
Provides :
Provides:
* `struct lw_terminal_vt100 *lw_terminal_vt100_init(void *user_data, void (*unimplemented)(struct lw_terminal* term_emul, char *seq, char chr));`
* `char lw_terminal_vt100_get(struct lw_terminal_vt100 *vt100, unsigned int x, unsigned int y);`
@ -154,7 +182,8 @@ Provides :
Forks a program, plug its io to a pseudo terminal and emulate a vt100
using `lw_terminal_vt100`.
Provides :
Provides:
* `void vt100_headless_fork(struct vt100_headless *this, const char *progname, char *const argv[]);`
* `struct vt100_headless *vt100_headless_init(void);`
* `const char **vt100_headless_getlines(struct vt100_headless *this);`

30
example/top.c Normal file
View File

@ -0,0 +1,30 @@
#include <stdio.h>
#include <stdlib.h>
#include "hl_vt100.h"
void changed(struct vt100_headless *vt100)
{
const char **lines;
lines = vt100_headless_getlines(vt100);
for (unsigned int y = 0; y < vt100->term->height; ++y)
{
write(1, "|", 1);
write(1, lines[y], vt100->term->width);
write(1, "|\n", 2);
}
write(1, "\n", 1);
}
int main(int ac, char **av)
{
struct vt100_headless *vt100;
char *argv[] = {"top", NULL};
vt100 = new_vt100_headless();
vt100_headless_fork(vt100, argv[0], argv);
vt100->changed = changed;
vt100_headless_main_loop(vt100);
return EXIT_SUCCESS;
}

19
example/top.py Normal file
View File

@ -0,0 +1,19 @@
import hl_vt100
def dump(vt100):
print("" + "" * vt100.width + "")
for line in vt100.getlines():
print(f"{line:{vt100.width}}")
print("" + "" * vt100.width + "")
def main():
vt100 = hl_vt100.vt100_headless()
vt100.changed_callback = lambda: dump(vt100)
vt100.fork('top', ['top'])
vt100.main_loop()
if __name__ == '__main__':
main()

View File

@ -1,85 +0,0 @@
%module hl_vt100
/*
* Copyright (c) 2016 Julien Palard.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
%{
#include "src/lw_terminal_vt100.h"
#include "src/hl_vt100.h"
%}
%typemap(in) char ** {
/* Check if is a list */
if (PyList_Check($input)) {
int size = PyList_Size($input);
int i = 0;
$1 = (char **) malloc((size+1)*sizeof(char *));
for (i = 0; i < size; i++) {
PyObject *o = PyList_GetItem($input,i);
if (PyString_Check(o))
$1[i] = PyString_AsString(PyList_GetItem($input,i));
else {
PyErr_SetString(PyExc_TypeError,"list must contain strings");
free($1);
return NULL;
}
}
$1[i] = 0;
} else {
PyErr_SetString(PyExc_TypeError,"not a list");
return NULL;
}
}
%typemap(out) char ** {
/* Check if is a list */
int i;
$result = PyList_New(0);
for (i = 0; i < arg1->term->height; i++)
PyList_Append($result, PyString_FromStringAndSize($1[i], arg1->term->width));
}
// This cleans up the char ** array we malloc'd before the function call
%typemap(freearg) char ** {
free((char *) $1);
}
struct vt100_headless
{
void (*changed)(struct vt100_headless *this);
%immutable;
int master;
struct termios backup;
struct lw_terminal_vt100 *term;
%extend {
vt100_headless();
~vt100_headless();
void fork(const char *progname, char **argv);
char **getlines();
int main_loop();
void stop();
}
};

View File

@ -25,27 +25,15 @@
if [ "$1" = python ]
then
if ! which swig > /dev/null
then
echo "You should install swig !"
exit 1
fi
make python_module
if [ $? != 0 ]
then
echo "Failed to build python module"
exit 1
fi
python -m pip install .
cat <<EOF | python
import hl_vt100
import time
import sys
print "Starting python test..."
print("Starting python test...")
vt100 = hl_vt100.vt100_headless()
vt100.fork('/usr/bin/top', ['/usr/bin/top', '-n', '1'])
vt100.main_loop()
[sys.stdout.write(line + "\n") for line in vt100.getlines()]
print(*vt100.getlines(), sep="\n")
EOF
exit
fi
@ -55,7 +43,7 @@ make clean
if [ "$1" = c ]
then
make && make test
LD_LIBRARY_PATH=. ./test /usr/bin/top
LD_LIBRARY_PATH=. ./test /usr/bin/top -n 1
exit
fi

View File

@ -7,8 +7,10 @@ setup.py file for hl_vt100
from distutils.core import setup, Extension
hl_vt100_module = Extension('_hl_vt100',
sources=['hl_vt100_wrap.c',
hl_vt100_module = Extension('hl_vt100',
include_dirs=['src'],
define_macros=[('NDEBUG', '1')],
sources=['src/vt100_module.c',
'src/hl_vt100.c',
'src/lw_terminal_parser.c',
'src/lw_terminal_vt100.c'])
@ -20,6 +22,7 @@ setup(name='hl_vt100',
author_email='julien@palard.fr',
description="""Headless vt100 emulator""",
ext_modules=[hl_vt100_module],
include_package_data=True,
py_modules=["hl_vt100"],
classifiers=[
"Programming Language :: C",

281
src/vt100_module.c Normal file
View File

@ -0,0 +1,281 @@
/* This is a Python wrapper of the vt100 headless library */
/*
occurrences of 'xx' should be changed to something reasonable for your
module. If your module is named foo your sourcefile should be named
foomodule.c.
You will probably want to delete all references to 'x_attr' and add
your own types of attributes instead. Maybe you want to name your
local variables other than 'self'. If your object type is needed in
other files, you'll have to create a file "foobarobject.h"; see
floatobject.h for an example. */
#include <stddef.h>
#include "Python.h"
#include "structmember.h"
#include "lw_terminal_vt100.h"
#include "hl_vt100.h"
typedef struct {
PyObject_HEAD
struct vt100_headless *obj;
PyObject *changed_callback;
} VT100Object;
static PyTypeObject VT100_Type;
#define VT100Object_Check(v) Py_IS_TYPE(v, &VT100_Type)
VT100Object **allocated;
size_t allocated_size;
/* VT100 methods */
PyDoc_STRVAR(vt100_headless_fork_doc,
"fork(progname, argv)\n\
\n\
Fork a process in a new PTY handled by an headless VT100 emulator.");
static PyObject *
VT100_fork(VT100Object *self, PyObject *args)
{
char *progname;
PyObject *pyargv;
const char **argv;
int argc;
int i;
if (!PyArg_ParseTuple(args, "sO:fork", &progname, &pyargv))
return NULL;
if (!PyList_Check(pyargv))
{
PyErr_SetString(PyExc_TypeError, "not a list");
return NULL;
}
argc = PyList_Size(pyargv);
for (i = 0; i < argc; i++)
{
PyObject *o = PyList_GetItem(pyargv, i);
if (!PyUnicode_Check(o))
{
PyErr_SetString(PyExc_TypeError, "argv list must contain strings");
return NULL;
}
}
argv = PyMem_Calloc(argc + 1, sizeof(char *));
for (i = 0; i < argc; i++)
argv[i] = PyUnicode_AsUTF8(PyList_GetItem(pyargv, i));
argv[i] = NULL;
vt100_headless_fork(self->obj, progname, (char **)argv);
PyMem_Free(argv);
Py_RETURN_NONE;
}
PyDoc_STRVAR(vt100_headless_getlines_doc,
"getlines()\n\
\n\
Get a list of lines as currently seen by the emulator.");
static PyObject *
VT100_getlines(VT100Object *self, PyObject *Py_UNUSED(ignored))
{
const char **lines;
PyObject *result;
lines = vt100_headless_getlines(self->obj);
result = PyList_New(0);
if (result == NULL)
return NULL;
for (unsigned int i = 0; i < self->obj->term->height; i++)
PyList_Append(result, PyUnicode_FromStringAndSize(lines[i], self->obj->term->width));
return result;
}
PyDoc_STRVAR(vt100_headless_main_loop_doc,
"main_loop()\n\
\n\
Enter the emulator main loop.");
static PyObject *
VT100_main_loop(VT100Object *self, PyObject *Py_UNUSED(ignored))
{
vt100_headless_main_loop(self->obj);
if (PyErr_Occurred())
return NULL;
Py_RETURN_NONE;
}
PyDoc_STRVAR(vt100_headless_stop_doc,
"stop()\n\
\n\
Stop emulator main loop.");
static PyObject *
VT100_stop(VT100Object *self, PyObject *Py_UNUSED(ignored))
{
vt100_headless_stop(self->obj);
Py_RETURN_NONE;
}
static int
vt100_add_to_allocated(VT100Object *obj)
{
for (size_t i = 0; i < allocated_size; i++)
{
if (allocated[i] == NULL)
{
allocated[i] = obj;
return 0;
}
}
/* Out of allocated memory, realloc. */
allocated_size *= 2;
allocated = PyMem_Realloc(allocated, allocated_size * sizeof(VT100Object*));
if (allocated == NULL)
{
PyErr_SetString(PyExc_MemoryError, "cannot allocate vt100 emulator");
return -1;
}
return vt100_add_to_allocated(obj);
}
static int
vt100_del_from_allocated(VT100Object *obj)
{
for (size_t i = 0; i < allocated_size; i++)
{
if (allocated[i] == obj)
{
allocated[i] = NULL;
return 0;
}
}
return -1;
}
VT100Object *
vt100_find_in_allocated(struct vt100_headless *obj)
{
for (size_t i = 0; i < allocated_size; i++)
if (allocated[i]->obj == obj)
return allocated[i];
return NULL;
}
void
hl_vt100_changed_cb(struct vt100_headless *this)
{
VT100Object *obj;
PyObject *result;
obj = vt100_find_in_allocated(this);
if (obj->changed_callback != NULL && obj->changed_callback != Py_None)
{
result = PyObject_CallNoArgs(obj->changed_callback);
if (result == NULL)
this->should_quit = 1;
}
}
static int
VT100_init(VT100Object *self, PyObject *args, PyObject *kwds)
{
self->obj = new_vt100_headless();
vt100_add_to_allocated(self);
self->obj->changed = hl_vt100_changed_cb;
if (self->obj == NULL)
{
PyErr_SetString(PyExc_MemoryError, "cannot allocate vt100 emulator");
return -1;
}
return 0;
}
static PyObject *
VT100_getwidth(VT100Object *self, void *closure)
{
return PyLong_FromUnsignedLong(self->obj->term->width);
}
static PyObject *
VT100_getheight(VT100Object *self, void *closure)
{
return PyLong_FromUnsignedLong(self->obj->term->height);
}
static void
VT100_dealloc(VT100Object *self)
{
vt100_del_from_allocated(self);
Py_XDECREF(self->changed_callback);
delete_vt100_headless(self->obj);
Py_TYPE(self)->tp_free((PyObject *)self);
}
static PyMethodDef VT100_methods[] = {
{"fork", (PyCFunction)VT100_fork, METH_VARARGS, vt100_headless_fork_doc},
{"getlines", (PyCFunction)VT100_getlines, METH_NOARGS, vt100_headless_getlines_doc},
{"main_loop", (PyCFunction)VT100_main_loop, METH_NOARGS, vt100_headless_main_loop_doc},
{"stop", (PyCFunction)VT100_stop, METH_NOARGS, vt100_headless_stop_doc},
{NULL, NULL} /* sentinel */
};
static PyMemberDef VT100_members[] = {
{"changed_callback", T_OBJECT_EX, offsetof(VT100Object, changed_callback), 0,
"Changed Callback"},
{NULL} /* Sentinel */
};
static PyGetSetDef VT100_getsetters[] = {
{"width", (getter) VT100_getwidth, NULL, "Terminal width", NULL},
{"height", (getter) VT100_getheight, NULL, "Terminal height", NULL},
{NULL} /* Sentinel */
};
static PyTypeObject VT100_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "hl_vt100.vt100_headless",
.tp_basicsize = sizeof(VT100Object),
.tp_dealloc = (destructor)VT100_dealloc,
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
.tp_methods = VT100_methods,
.tp_init = (initproc)VT100_init,
.tp_new = PyType_GenericNew,
.tp_members = VT100_members,
.tp_getset = VT100_getsetters,
};
PyDoc_STRVAR(module_doc,
"Headless VT100 Terminal Emulator.");
static struct PyModuleDef hl_vt100_module = {
PyModuleDef_HEAD_INIT,
.m_name = "hl_vt100",
.m_doc = module_doc,
};
PyMODINIT_FUNC
PyInit_hl_vt100(void)
{
PyObject *m;
m = PyModule_Create(&hl_vt100_module);
allocated = PyMem_Calloc(4096, sizeof(VT100Object*));
allocated_size = 4096;
if (m == NULL)
return NULL;
if (PyModule_AddType(m, &VT100_Type) < 0) {
Py_DECREF(m);
return NULL;
}
return m;
}