From 0e9115e34305d5a703a72557d0830e2d0ed385ac Mon Sep 17 00:00:00 2001
From: John Estabrook <jestabro@vyos.io>
Date: Sun, 31 Jul 2022 14:33:44 -0500
Subject: graphql: T4580: handle case of op-mode script name containing hyphens

---
 src/services/api/graphql/recipes/session.py        | 18 ++++++-------
 .../api/graphql/utils/schema_from_op_mode.py       |  2 +-
 src/services/api/graphql/utils/util.py             | 31 ++++++++++++++++++----
 3 files changed, 36 insertions(+), 15 deletions(-)

(limited to 'src')

diff --git a/src/services/api/graphql/recipes/session.py b/src/services/api/graphql/recipes/session.py
index 6b580af01..ac185beb7 100644
--- a/src/services/api/graphql/recipes/session.py
+++ b/src/services/api/graphql/recipes/session.py
@@ -40,7 +40,7 @@ class Session:
 
         try:
             with open(op_mode_include_file) as f:
-                self._op_mode_list = f.read()
+                self._op_mode_list = json.loads(f.read())
         except Exception:
             self._op_mode_list = None
 
@@ -171,11 +171,11 @@ class Session:
         # handle the case that the op-mode file contains underscores:
         if op_mode_list is None:
             raise FileNotFoundError(f"No op-mode file list at '{op_mode_include_file}'")
-        (func_name, basename) = split_compound_op_mode_name(name, op_mode_list)
-        if basename == '':
-            raise FileNotFoundError(f"No op-mode file basename in string '{name}'")
+        (func_name, scriptname) = split_compound_op_mode_name(name, op_mode_list)
+        if scriptname == '':
+            raise FileNotFoundError(f"No op-mode file named in string '{name}'")
 
-        mod = load_op_mode_as_module(f'{basename}.py')
+        mod = load_op_mode_as_module(f'{scriptname}')
         func = getattr(mod, func_name)
         if len(list(data)) > 0:
             res = func(True, **data)
@@ -193,11 +193,11 @@ class Session:
         # handle the case that the op-mode file name contains underscores:
         if op_mode_list is None:
             raise FileNotFoundError(f"No op-mode file list at '{op_mode_include_file}'")
-        (func_name, basename) = split_compound_op_mode_name(name, op_mode_list)
-        if basename == '':
-            raise FileNotFoundError(f"No op-mode file basename in string '{name}'")
+        (func_name, scriptname) = split_compound_op_mode_name(name, op_mode_list)
+        if scriptname == '':
+            raise FileNotFoundError(f"No op-mode file named in string '{name}'")
 
-        mod = load_op_mode_as_module(f'{basename}.py')
+        mod = load_op_mode_as_module(f'{scriptname}')
         func = getattr(mod, func_name)
         if len(list(data)) > 0:
             res = func(**data)
diff --git a/src/services/api/graphql/utils/schema_from_op_mode.py b/src/services/api/graphql/utils/schema_from_op_mode.py
index cdde5f187..d27586747 100755
--- a/src/services/api/graphql/utils/schema_from_op_mode.py
+++ b/src/services/api/graphql/utils/schema_from_op_mode.py
@@ -138,7 +138,7 @@ def generate_op_mode_definitions():
         op_mode_files = json.load(f)
 
     for file in op_mode_files:
-        basename = os.path.splitext(file)[0]
+        basename = os.path.splitext(file)[0].replace('-', '_')
         module = load_as_module(basename, os.path.join(OP_MODE_PATH, file))
 
         funcs = getmembers(module, isfunction)
diff --git a/src/services/api/graphql/utils/util.py b/src/services/api/graphql/utils/util.py
index e3dea31bf..073126853 100644
--- a/src/services/api/graphql/utils/util.py
+++ b/src/services/api/graphql/utils/util.py
@@ -27,7 +27,7 @@ def load_as_module(name: str, path: str):
 
 def load_op_mode_as_module(name: str):
     path = os.path.join(directories['op_mode'], name)
-    name = os.path.splitext(name)[0]
+    name = os.path.splitext(name)[0].replace('-', '_')
     return load_as_module(name, path)
 
 def is_op_mode_function_name(name):
@@ -40,16 +40,37 @@ def is_show_function_name(name):
         return True
     return False
 
+def _nth_split(delim: str, n: int, s: str):
+    groups = s.split(delim)
+    l = len(groups)
+    if n > l-1 or n < 1:
+        return (s, '')
+    return (delim.join(groups[:n]), delim.join(groups[n:]))
+
 def _nth_rsplit(delim: str, n: int, s: str):
     groups = s.split(delim)
     l = len(groups)
-    if n > l-1:
-        return ('', s)
+    if n > l-1 or n < 1:
+        return (s, '')
     return (delim.join(groups[:l-n]), delim.join(groups[l-n:]))
 
+# Since we have mangled possible hyphens in the file name while constructing
+# the snake case of the query/mutation name, we will need to recover the
+# file name by searching with mangling:
+def _filter_on_mangled(test):
+    def func(elem):
+        mangle = os.path.splitext(elem)[0].replace('-', '_')
+        return test == mangle
+    return func
+
+# Find longest name in concatenated string that matches the basename of an
+# op-mode script. Should one prefer to concatenate in the reverse order
+# (script_name + '_' + function_name), use _nth_rsplit.
 def split_compound_op_mode_name(name: str, files: list):
     for i in range(1, name.count('_') + 1):
-        pair = _nth_rsplit('_', i, name)
-        if pair[1] in files:
+        pair = _nth_split('_', i, name)
+        f = list(filter(_filter_on_mangled(pair[1]), files))
+        if f:
+            pair = (pair[0], f[0])
             return pair
     return (name, '')
-- 
cgit v1.2.3