Repository: desihub/desiutil

Issue:
desiInstall creates incorrect module file for QuasarNP
QuasarNP is a semi-external package hosted in desihub.  It doesn't have a boilerplate etc/quasarnp.module file, but it also doesn't require anything custom so I was expecting the desiInstall default module file to work.  desiInstall does correctly identify the "py" install and pip installs QuasarNP, but the resulting module file doesn't add $PRODUCT_DIR/lib/python3.8/site-packages to $PYTHONPATH.
```
[cori06 ~] desiInstall -v -r $SCRATCH/desi/test QuasarNP 0.1.0
WARNING:install.py:183:get_options:2021-06-03T14:21:29: The environment variable LANG is not set!
DEBUG:install.py:251:get_options:2021-06-03T14:21:29: Set log level to DEBUG.
DEBUG:install.py:320:get_product_version:2021-06-03T14:21:29: Detected GitHub install.
DEBUG:install.py:351:identify_branch:2021-06-03T14:21:29: Using https://github.com/desihub/QuasarNP/archive/0.1.0.tar.gz as the URL of this product.
INFO:install.py:412:get_code:2021-06-03T14:21:29: Detected old working directory, /global/u2/s/sjbailey/QuasarNP-0.1.0. Deleting...
DEBUG:install.py:414:get_code:2021-06-03T14:21:29: shutil.rmtree('/global/u2/s/sjbailey/QuasarNP-0.1.0')
DEBUG:install.py:638:start_modules:2021-06-03T14:21:30: Initializing Modules with MODULESHOME=/opt/cray/pe/modules/3.2.11.4.
DEBUG:install.py:538:build_type:2021-06-03T14:21:30: Detected build type: py
DEBUG:install.py:700:install_module:2021-06-03T14:21:30: configure_module(QuasarNP, 0.1.0, working_dir=/global/u2/s/sjbailey/QuasarNP-0.1.0, dev=False)
DEBUG:install.py:720:install_module:2021-06-03T14:21:30: process_module('/global/common/software/desi/cori/desiconda/20200801-1.4.0-spec/code/desiutil/3.2.1/lib/python3.8/site-packages/desiutil/data/desiutil.module', self.module_keywords, '/global/cscratch1/sd/sjbailey/desi/test/modulefiles')
DEBUG:install.py:757:prepare_environment:2021-06-03T14:21:31: module('switch', 'QuasarNP/0.1.0')
DEBUG:install.py:538:build_type:2021-06-03T14:21:31: Detected build type: py
DEBUG:install.py:538:build_type:2021-06-03T14:21:31: Detected build type: py
DEBUG:install.py:802:install:2021-06-03T14:21:31: os.makedirs('/global/cscratch1/sd/sjbailey/desi/test/code/QuasarNP/0.1.0/lib/python3.8/site-packages')
DEBUG:install.py:824:install:2021-06-03T14:21:31: /global/common/software/desi/cori/desiconda/20200801-1.4.0-spec/conda/bin/python -m pip install --no-deps --disable-pip-version-check --ignore-installed --prefix=/global/cscratch1/sd/sjbailey/desi/test/code/QuasarNP/0.1.0 .
DEBUG:install.py:833:install:2021-06-03T14:21:33: Processing /global/u2/s/sjbailey/QuasarNP-0.1.0
Building wheels for collected packages: quasarnp
  Building wheel for quasarnp (setup.py): started
  Building wheel for quasarnp (setup.py): finished with status 'done'
  Created wheel for quasarnp: filename=quasarnp-0.1.0-py3-none-any.whl size=13407 sha256=17d7dbdf89520f3a0e5d751edd4b7591b45563425a190550ff31c71e48b1b855
  Stored in directory: /global/u2/s/sjbailey/.cache/pip/wheels/b3/5d/19/08d052aecb141666e9fca7cef889a0c9393b18766c47f69300
Successfully built quasarnp
Installing collected packages: quasarnp
Successfully installed quasarnp-0.1.0

DEBUG:install.py:538:build_type:2021-06-03T14:21:33: Detected build type: py
DEBUG:install.py:538:build_type:2021-06-03T14:21:33: Detected build type: py
DEBUG:install.py:957:permissions:2021-06-03T14:21:33: fix_permissions.sh -v /global/cscratch1/sd/sjbailey/desi/test/code/QuasarNP/0.1.0
DEBUG:install.py:962:permissions:2021-06-03T14:21:35: Fixing permissions on /global/cscratch1/sd/sjbailey/desi/test/code/QuasarNP/0.1.0 ...
/usr/bin/find /global/cscratch1/sd/sjbailey/desi/test/code/QuasarNP/0.1.0 -user sjbailey -not -group desi -exec chgrp -c -h desi {} ;
changed group of '/global/cscratch1/sd/sjbailey/desi/test/code/QuasarNP/0.1.0' from sjbailey to desi
changed group of '/global/cscratch1/sd/sjbailey/desi/test/code/QuasarNP/0.1.0/lib' from sjbailey to desi
changed group of '/global/cscratch1/sd/sjbailey/desi/test/code/QuasarNP/0.1.0/lib/python3.8' from sjbailey to desi
changed group of '/global/cscratch1/sd/sjbailey/desi/test/code/QuasarNP/0.1.0/lib/python3.8/site-packages' from sjbailey to desi
changed group of '/global/cscratch1/sd/sjbailey/desi/test/code/QuasarNP/0.1.0/lib/python3.8/site-packages/quasarnp' from sjbailey to desi
changed group of '/global/cscratch1/sd/sjbailey/desi/test/code/QuasarNP/0.1.0/lib/python3.8/site-packages/quasarnp/model.py' from sjbailey to desi
changed group of '/global/cscratch1/sd/sjbailey/desi/test/code/QuasarNP/0.1.0/lib/python3.8/site-packages/quasarnp/io.py' from sjbailey to desi
changed group of '/global/cscratch1/sd/sjbailey/desi/test/code/QuasarNP/0.1.0/lib/python3.8/site-packages/quasarnp/__init__.py' from sjbailey to desi
changed group of '/global/cscratch1/sd/sjbailey/desi/test/code/QuasarNP/0.1.0/lib/python3.8/site-packages/quasarnp/__pycache__' from sjbailey to desi
changed group of '/global/cscratch1/sd/sjbailey/desi/test/code/QuasarNP/0.1.0/lib/python3.8/site-packages/quasarnp/__pycache__/layers.cpython-38.pyc' from sjbailey to desi
changed group of '/global/cscratch1/sd/sjbailey/desi/test/code/QuasarNP/0.1.0/lib/python3.8/site-packages/quasarnp/__pycache__/model.cpython-38.pyc' from sjbailey to desi
changed group of '/global/cscratch1/sd/sjbailey/desi/test/code/QuasarNP/0.1.0/lib/python3.8/site-packages/quasarnp/__pycache__/__init__.cpython-38.pyc' from sjbailey to desi
changed group of '/global/cscratch1/sd/sjbailey/desi/test/code/QuasarNP/0.1.0/lib/python3.8/site-packages/quasarnp/__pycache__/utils.cpython-38.pyc' from sjbailey to desi
changed group of '/global/cscratch1/sd/sjbailey/desi/test/code/QuasarNP/0.1.0/lib/python3.8/site-packages/quasarnp/__pycache__/io.cpython-38.pyc' from sjbailey to desi
changed group of '/global/cscratch1/sd/sjbailey/desi/test/code/QuasarNP/0.1.0/lib/python3.8/site-packages/quasarnp/layers.py' from sjbailey to desi
changed group of '/global/cscratch1/sd/sjbailey/desi/test/code/QuasarNP/0.1.0/lib/python3.8/site-packages/quasarnp/utils.py' from sjbailey to desi
changed group of '/global/cscratch1/sd/sjbailey/desi/test/code/QuasarNP/0.1.0/lib/python3.8/site-packages/quasarnp-0.1.0.dist-info' from sjbailey to desi
changed group of '/global/cscratch1/sd/sjbailey/desi/test/code/QuasarNP/0.1.0/lib/python3.8/site-packages/quasarnp-0.1.0.dist-info/INSTALLER' from sjbailey to desi
changed group of '/global/cscratch1/sd/sjbailey/desi/test/code/QuasarNP/0.1.0/lib/python3.8/site-packages/quasarnp-0.1.0.dist-info/WHEEL' from sjbailey to desi
changed group of '/global/cscratch1/sd/sjbailey/desi/test/code/QuasarNP/0.1.0/lib/python3.8/site-packages/quasarnp-0.1.0.dist-info/LICENSE' from sjbailey to desi
changed group of '/global/cscratch1/sd/sjbailey/desi/test/code/QuasarNP/0.1.0/lib/python3.8/site-packages/quasarnp-0.1.0.dist-info/top_level.txt' from sjbailey to desi
changed group of '/global/cscratch1/sd/sjbailey/desi/test/code/QuasarNP/0.1.0/lib/python3.8/site-packages/quasarnp-0.1.0.dist-info/METADATA' from sjbailey to desi
changed group of '/global/cscratch1/sd/sjbailey/desi/test/code/QuasarNP/0.1.0/lib/python3.8/site-packages/quasarnp-0.1.0.dist-info/RECORD' from sjbailey to desi
/usr/bin/find /global/cscratch1/sd/sjbailey/desi/test/code/QuasarNP/0.1.0 -user sjbailey -type f -not -perm /g+r -exec chmod -c g+r {} ;
/usr/bin/find /global/cscratch1/sd/sjbailey/desi/test/code/QuasarNP/0.1.0 -user sjbailey -type d -not -perm -g+rxs -exec chmod -c g+rxs {} ;
mode of '/global/cscratch1/sd/sjbailey/desi/test/code/QuasarNP/0.1.0' changed from 0755 (rwxr-xr-x) to 2755 (rwxr-sr-x)
mode of '/global/cscratch1/sd/sjbailey/desi/test/code/QuasarNP/0.1.0/lib' changed from 0755 (rwxr-xr-x) to 2755 (rwxr-sr-x)
mode of '/global/cscratch1/sd/sjbailey/desi/test/code/QuasarNP/0.1.0/lib/python3.8' changed from 0755 (rwxr-xr-x) to 2755 (rwxr-sr-x)
mode of '/global/cscratch1/sd/sjbailey/desi/test/code/QuasarNP/0.1.0/lib/python3.8/site-packages' changed from 0755 (rwxr-xr-x) to 2755 (rwxr-sr-x)
mode of '/global/cscratch1/sd/sjbailey/desi/test/code/QuasarNP/0.1.0/lib/python3.8/site-packages/quasarnp' changed from 0755 (rwxr-xr-x) to 2755 (rwxr-sr-x)
mode of '/global/cscratch1/sd/sjbailey/desi/test/code/QuasarNP/0.1.0/lib/python3.8/site-packages/quasarnp/__pycache__' changed from 0755 (rwxr-xr-x) to 2755 (rwxr-sr-x)
mode of '/global/cscratch1/sd/sjbailey/desi/test/code/QuasarNP/0.1.0/lib/python3.8/site-packages/quasarnp-0.1.0.dist-info' changed from 0755 (rwxr-xr-x) to 2755 (rwxr-sr-x)

DEBUG:install.py:970:permissions:2021-06-03T14:21:35: chmod -R a-w /global/cscratch1/sd/sjbailey/desi/test/code/QuasarNP/0.1.0
DEBUG:install.py:975:permissions:2021-06-03T14:21:35: 
DEBUG:install.py:987:cleanup:2021-06-03T14:21:35: os.chdir('/global/u2/s/sjbailey')
DEBUG:install.py:991:cleanup:2021-06-03T14:21:35: shutil.rmtree('/global/u2/s/sjbailey/QuasarNP-0.1.0')
DEBUG:install.py:1024:run:2021-06-03T14:21:35: run() complete.
[cori06 ~] tail -15 $SCRATCH/desi/test/modulefiles/QuasarNP/0.1.0 
#
setenv [string toupper $product] $PRODUCT_DIR
#
# The lines below set various other environment variables.  They assume the
# template product layout.  These will be set or commented as needed by the
# desiInstall script.
#
# prepend-path PATH $PRODUCT_DIR/bin
# prepend-path PYTHONPATH $PRODUCT_DIR/lib/python3.8/site-packages
# prepend-path PYTHONPATH $PRODUCT_DIR/py
# prepend-path LD_LIBRARY_PATH $PRODUCT_DIR/lib
# prepend-path IDL_PATH +$PRODUCT_DIR/pro
#
# Add any non-standard Module code below this point.
#
```

Comments:

Patch:
diff --git a/doc/changes.rst b/doc/changes.rst
index ef3bf1d..0fc8189 100644
--- a/doc/changes.rst
+++ b/doc/changes.rst
@@ -5,7 +5,10 @@ Change Log
 3.2.2 (unreleased)
 ------------------
 
-* No changes yet.
+* Add module config support for packages like QuasarNP where the GitHub
+  name is capitalized by the python package isn't (PR `#173`_).
+
+.. _`#173`: https://github.com/desihub/desiutil/pull/173
 
 3.2.1 (2021-05-13)
 ------------------
diff --git a/py/desiutil/modules.py b/py/desiutil/modules.py
index 33a2ca9..4accae3 100644
--- a/py/desiutil/modules.py
+++ b/py/desiutil/modules.py
@@ -199,7 +199,10 @@ def configure_module(product, version, product_root, working_dir=None, dev=False
         module_keywords['needs_ld_lib'] = ''
     if isdir(join(working_dir, 'pro')):
         module_keywords['needs_idl'] = ''
-    if (exists(join(working_dir, 'setup.py')) and isdir(join(working_dir, product))):
+    if (exists(join(working_dir, 'setup.py')) and
+        (isdir(join(working_dir, product)) or
+         isdir(join(working_dir, product.lower())))
+        ):
         if dev:
             module_keywords['needs_trunk_py'] = ''
             module_keywords['trunk_py_dir'] = ''


Test patch:
diff --git a/py/desiutil/test/test_modules.py b/py/desiutil/test/test_modules.py
index 3b5233b..4c710f0 100644
--- a/py/desiutil/test/test_modules.py
+++ b/py/desiutil/test/test_modules.py
@@ -222,6 +222,35 @@ class TestModules(unittest.TestCase):
             rmdir(join(self.data_dir, t))
         for t in test_files:
             remove(join(self.data_dir, t))
+        #
+        # Test mixed case product directory (Blat) vs. python package (blat)
+        #
+        test_dirs = ('blat',)
+        test_files = {'setup.py': '#!/usr/bin/env python\n'}
+        for t in test_dirs:
+            mkdir(join(self.data_dir, t))
+        for t in test_files:
+            with open(join(self.data_dir, t), 'w') as s:
+                s.write(test_files[t])
+        results['name'] = 'Blat'
+        results['version'] = '1.2.3'
+        results['needs_bin'] = '# '
+        results['needs_python'] = ''
+        results['needs_trunk_py'] = '# '
+        results['trunk_py_dir'] = '/py'
+        results['needs_ld_lib'] = '# '
+        results['needs_idl'] = '# '
+
+        conf = configure_module('Blat', '1.2.3', '/my/product/root',
+                                working_dir=self.data_dir)
+
+        for key in results:
+            self.assertEqual(conf[key], results[key], key)
+        for t in test_dirs:
+            rmdir(join(self.data_dir, t))
+        for t in test_files:
+            remove(join(self.data_dir, t))
+
 
     def test_process_module(self):
         """Test processing of module file templates.