From d3d57aa61bb44fd0bdad460c8c173743ca808733 Mon Sep 17 00:00:00 2001
From: Cyrille Bagard <nocbos@gmail.com>
Date: Tue, 30 Jun 2020 00:37:58 +0200
Subject: Improved some common helpers inside the Python API.

---
 plugins/pychrysalide/common/fnv1a.c    | 36 ++++++++++++++---------
 plugins/pychrysalide/common/module.c   |  8 ++++-
 plugins/pychrysalide/common/pathname.c | 53 ++++++++++++++++++++++------------
 tests/common/fnv1a.py                  | 25 ++++++++++++++++
 tests/common/pathname.py               | 33 +++++++++++++++------
 5 files changed, 113 insertions(+), 42 deletions(-)
 create mode 100644 tests/common/fnv1a.py

diff --git a/plugins/pychrysalide/common/fnv1a.c b/plugins/pychrysalide/common/fnv1a.c
index c24eb6e..381af78 100644
--- a/plugins/pychrysalide/common/fnv1a.c
+++ b/plugins/pychrysalide/common/fnv1a.c
@@ -36,6 +36,14 @@
 
 
 
+#define FNV1A_DOC                                           \
+    "Python version for Chrysalide of the Fowler-Noll-Vo"   \
+    " hash function.\n"                                     \
+    "\n"                                                    \
+    "There is no constructor for this class: its methods"   \
+    " are static methods only."
+
+
 /* Détermine l'empreinte FNV1a d'une chaîne de caractères. */
 static PyObject *py_fnv1a_hash(PyObject *, PyObject *);
 
@@ -61,6 +69,13 @@ static PyObject *py_fnv1a_hash(PyObject *self, PyObject *args)
     int ret;                                /* Bilan de lecture des args.  */
     fnv64_t value;                          /* Empreinte calculée          */
 
+#define FNV1A_HASH_METHOD PYTHON_METHOD_DEF                 \
+(                                                           \
+    hash, "str, /",                                         \
+    METH_VARARGS | METH_STATIC, py_fnv1a,                   \
+    "Compute the FNV-1a hash from a given string."          \
+)
+
     ret = PyArg_ParseTuple(args, "s", &str);
     if (!ret) return NULL;
 
@@ -88,27 +103,24 @@ static PyObject *py_fnv1a_hash(PyObject *self, PyObject *args)
 PyTypeObject *get_python_fnv1a_type(void)
 {
     static PyMethodDef py_fnv1a_methods[] = {
-
-        { "hash", py_fnv1a_hash,
-          METH_VARARGS | METH_STATIC,
-          "hash(str, /)\n--\n\nCompute the FNV-1a hash from a given string."
-        },
+        FNV1A_HASH_METHOD,
         { NULL }
-
     };
 
     static PyTypeObject py_fnv1a_type = {
 
         PyVarObject_HEAD_INIT(NULL, 0)
 
-        .tp_name = "pychrysalide.core.fnv1a",
-        .tp_basicsize = sizeof(PyObject),
+        .tp_name        = "pychrysalide.common.fnv1a",
+        .tp_basicsize   = sizeof(PyObject),
+
+        .tp_flags       = Py_TPFLAGS_DEFAULT,
 
-        .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IS_ABSTRACT,
+        .tp_doc         = FNV1A_DOC,
 
-        .tp_doc = "Python version for Chrysalide of the Fowler-Noll-Vo hash function.",
+        .tp_methods     =  py_fnv1a_methods,
 
-        .tp_methods =  py_fnv1a_methods
+        .tp_new         = no_python_constructor_allowed,
 
     };
 
@@ -138,8 +150,6 @@ bool ensure_python_fnv1a_is_registered(void)
 
     if (!PyType_HasFeature(type, Py_TPFLAGS_READY))
     {
-        //type->tp_new = PyType_GenericNew;
-
         if (PyType_Ready(type) != 0)
             return false;
 
diff --git a/plugins/pychrysalide/common/module.c b/plugins/pychrysalide/common/module.c
index e8cda4f..7f7cbb7 100644
--- a/plugins/pychrysalide/common/module.c
+++ b/plugins/pychrysalide/common/module.c
@@ -50,12 +50,18 @@ bool add_common_module(PyObject *super)
     bool result;                            /* Bilan à retourner           */
     PyObject *module;                       /* Sous-module mis en place    */
 
+#define PYCHRYSALIDE_COMMON_DOC                                         \
+    "This module provides some tiny helpers for different use cases.\n" \
+    "\n"                                                                \
+    "The code for these features is shared between various parts of"    \
+    " Chrysalide."
+
     static PyModuleDef py_chrysalide_common_module = {
 
         .m_base = PyModuleDef_HEAD_INIT,
 
         .m_name = "pychrysalide.common",
-        .m_doc = "Python module for Chrysalide.common",
+        .m_doc  = PYCHRYSALIDE_COMMON_DOC,
 
         .m_size = -1,
 
diff --git a/plugins/pychrysalide/common/pathname.c b/plugins/pychrysalide/common/pathname.c
index 8a18ae3..c83c27d 100644
--- a/plugins/pychrysalide/common/pathname.c
+++ b/plugins/pychrysalide/common/pathname.c
@@ -40,6 +40,13 @@
 
 
 
+#define PYTHON_PATHNAME_DOC                                 \
+    "Path manipulations in Python for Chrysalide.\n"        \
+    "\n"                                                    \
+    "There is no constructor for this class: its methods"   \
+    " are static methods only."
+
+
 /* Calcule le chemin relatif entre deux fichiers donnés. */
 static PyObject *py_build_relative_filename(PyObject *, PyObject *);
 
@@ -69,6 +76,15 @@ static PyObject *py_build_relative_filename(PyObject *self, PyObject *args)
     int ret;                                /* Bilan de lecture des args.  */
     char *relative;                         /* Chemin d'accès construit    */
 
+#define BUILD_RELATIVE_FILENAME_METHOD PYTHON_METHOD_DEF    \
+(                                                           \
+    build_relative_filename, "ref, target",                 \
+    METH_VARARGS | METH_STATIC, py,                         \
+    "Compute the relative path between two files.\n"        \
+    "\n"                                                    \
+    "Both arguments must be strings."                       \
+)
+
     ret = PyArg_ParseTuple(args, "ss", &ref, &target);
     if (!ret) return NULL;
 
@@ -104,6 +120,15 @@ static PyObject *py_build_absolute_filename(PyObject *self, PyObject *args)
     int ret;                                /* Bilan de lecture des args.  */
     char *relative;                         /* Chemin d'accès construit    */
 
+#define BUILD_ABSOLUTE_FILENAME_METHOD PYTHON_METHOD_DEF    \
+(                                                           \
+    build_absolute_filename, "ref, target",                 \
+    METH_VARARGS | METH_STATIC, py,                         \
+    "Compute the absolute path for a file.\n"               \
+    "\n"                                                    \
+    "Both arguments must be strings."                       \
+)
+
     ret = PyArg_ParseTuple(args, "ss", &ref, &target);
     if (!ret) return NULL;
 
@@ -117,9 +142,7 @@ static PyObject *py_build_absolute_filename(PyObject *self, PyObject *args)
     else
     {
         result = PyUnicode_FromString(relative);
-
         free(relative);
-
     }
 
     return result;
@@ -142,31 +165,25 @@ static PyObject *py_build_absolute_filename(PyObject *self, PyObject *args)
 PyTypeObject *get_python_pathname_type(void)
 {
     static PyMethodDef py_pathname_methods[] = {
-
-        { "build_relative_filename", py_build_relative_filename,
-          METH_VARARGS | METH_STATIC,
-          "build_relative_filename(ref, target, /)\n--\n\nCompute the relative path between two files."
-        },
-        { "build_absolute_filename", py_build_absolute_filename,
-          METH_VARARGS | METH_STATIC,
-          "build_absolute_filename(ref, target, /)\n--\n\nCompute the absolute path for a file."
-        },
+        BUILD_RELATIVE_FILENAME_METHOD,
+        BUILD_ABSOLUTE_FILENAME_METHOD,
         { NULL }
-
     };
 
     static PyTypeObject py_pathname_type = {
 
         PyVarObject_HEAD_INIT(NULL, 0)
 
-        .tp_name = "pychrysalide.core.pathname",
-        .tp_basicsize = sizeof(PyObject),
+        .tp_name        = "pychrysalide.common.pathname",
+        .tp_basicsize   = sizeof(PyObject),
 
-        .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IS_ABSTRACT,
+        .tp_flags       = Py_TPFLAGS_DEFAULT,
 
-        .tp_doc = "Path manipulations in Python for Chrysalide.",
+        .tp_doc         = PYTHON_PATHNAME_DOC,
 
-        .tp_methods =  py_pathname_methods
+        .tp_methods     =  py_pathname_methods,
+
+        .tp_new         = no_python_constructor_allowed,
 
     };
 
@@ -196,8 +213,6 @@ bool ensure_python_pathname_is_registered(void)
 
     if (!PyType_HasFeature(type, Py_TPFLAGS_READY))
     {
-        //type->tp_new = PyType_GenericNew;
-
         if (PyType_Ready(type) != 0)
             return false;
 
diff --git a/tests/common/fnv1a.py b/tests/common/fnv1a.py
new file mode 100644
index 0000000..2013afa
--- /dev/null
+++ b/tests/common/fnv1a.py
@@ -0,0 +1,25 @@
+
+from chrysacase import ChrysalideTestCase
+from pychrysalide.common import fnv1a
+
+
+class TestFnv1a(ChrysalideTestCase):
+    """TestCase for common.fnv1a*"""
+
+    def testFnv1aConstructor(self):
+        """Check that no constructor is available for the fnv1a class."""
+
+        with self.assertRaisesRegex(NotImplementedError, 'Chrysalide does not allow building this kind of object from Python'):
+            instance = fnv1a()
+
+
+    def testFnv1aSamples(self):
+        """Compute some Fnv1a hashs."""
+
+        # Test cases from http://isthe.com/chongo/src/fnv/test_fnv.c
+
+        val = fnv1a.hash('')
+        self.assertEqual(val, 0xcbf29ce484222325)
+
+        val = fnv1a.hash('chongo was here!\n')
+        self.assertEqual(val, 0x46810940eff5f915)
diff --git a/tests/common/pathname.py b/tests/common/pathname.py
index 3692434..00a084b 100644
--- a/tests/common/pathname.py
+++ b/tests/common/pathname.py
@@ -1,21 +1,15 @@
-#!/usr/bin/python3-dbg
-# -*- coding: utf-8 -*-
-
-
-# Tests minimalistes pour valider la construction de chemins relatifs et absolus.
-
 
 from chrysacase import ChrysalideTestCase
 from pychrysalide.common import pathname
 
 
-class TestPathNames(ChrysalideTestCase):
-    """TestCase for common.pathname.build*"""
+class TestPathnames(ChrysalideTestCase):
+    """TestCase for common.pathname*"""
 
     @classmethod
     def setUpClass(cls):
 
-        super(TestPathNames, cls).setUpClass()
+        super(TestPathnames, cls).setUpClass()
 
         cls._tests = [
             {
@@ -93,3 +87,24 @@ class TestPathNames(ChrysalideTestCase):
         with self.assertRaisesRegex(Exception, 'Relative path is too deep.'):
 
             got = build_absolute('/a/b', '../../c')
+
+
+    def testPathnameConstructor(self):
+        """Check that no constructor is available for the pathname class."""
+
+        with self.assertRaisesRegex(NotImplementedError, 'Chrysalide does not allow building this kind of object from Python'):
+
+            instance = pathname()
+
+
+    def testPathnameSamples(self):
+        """Play with some path samples."""
+
+        filename = pathname.build_absolute_filename('/tmp/deep', '../myfile')
+        self.assertEqual(filename, '/myfile')
+
+        filename = pathname.build_absolute_filename('/tmp/deep/', '../myfile')
+        self.assertEqual(filename, '/tmp/myfile')
+
+        filename = pathname.build_relative_filename('/tmp/deep', '/myfile')
+        self.assertEqual(filename, '../myfile')
-- 
cgit v0.11.2-87-g4458