Prechádzať zdrojové kódy

Allowing config values to start with a newline

Cody Scott 8 rokov pred
rodič
commit
af19f40438

+ 53 - 41
nsist/__init__.py

@@ -64,11 +64,11 @@ class InputError(ValueError):
 
 
 class InstallerBuilder(object):
 class InstallerBuilder(object):
     """Controls building an installer. This includes three main steps:
     """Controls building an installer. This includes three main steps:
-    
+
     1. Arranging the necessary files in the build directory.
     1. Arranging the necessary files in the build directory.
     2. Filling out the template NSI file to control NSIS.
     2. Filling out the template NSI file to control NSIS.
     3. Running ``makensis`` to build the installer.
     3. Running ``makensis`` to build the installer.
-    
+
     :param str appname: Application name
     :param str appname: Application name
     :param str version: Application version
     :param str version: Application version
     :param dict shortcuts: Dictionary keyed by shortcut name, containing
     :param dict shortcuts: Dictionary keyed by shortcut name, containing
@@ -151,12 +151,12 @@ class InstallerBuilder(object):
                 self.nsi_template = 'pyapp_installpy.nsi'
                 self.nsi_template = 'pyapp_installpy.nsi'
 
 
         self.nsi_file = pjoin(self.build_dir, 'installer.nsi')
         self.nsi_file = pjoin(self.build_dir, 'installer.nsi')
-        
+
         # To be filled later
         # To be filled later
         self.install_files = []
         self.install_files = []
         self.install_dirs = []
         self.install_dirs = []
         self.msvcrt_files = []
         self.msvcrt_files = []
-    
+
     _py_version_pattern = re.compile(r'\d\.\d+\.\d+$')
     _py_version_pattern = re.compile(r'\d\.\d+\.\d+$')
 
 
     @property
     @property
@@ -166,7 +166,7 @@ class InstallerBuilder(object):
 
 
     def make_installer_name(self):
     def make_installer_name(self):
         """Generate the filename of the installer exe
         """Generate the filename of the installer exe
-        
+
         e.g. My_App_1.0.exe
         e.g. My_App_1.0.exe
         """
         """
         s = self.appname + '_' + self.version + '.exe'
         s = self.appname + '_' + self.version + '.exe'
@@ -192,7 +192,7 @@ class InstallerBuilder(object):
 
 
     def fetch_python(self):
     def fetch_python(self):
         """Fetch the MSI for the specified version of Python.
         """Fetch the MSI for the specified version of Python.
-        
+
         It will be placed in the build directory.
         It will be placed in the build directory.
         """
         """
         url, filename = self._python_download_url_filename()
         url, filename = self._python_download_url_filename()
@@ -243,7 +243,7 @@ class InstallerBuilder(object):
 
 
     def fetch_pylauncher(self):
     def fetch_pylauncher(self):
         """Fetch the MSI for PyLauncher (required for Python2.x).
         """Fetch the MSI for PyLauncher (required for Python2.x).
-    
+
         It will be placed in the build directory.
         It will be placed in the build directory.
         """
         """
         arch_tag = '.amd64' if (self.py_bitness == 64) else ''
         arch_tag = '.amd64' if (self.py_bitness == 64) else ''
@@ -288,10 +288,10 @@ if __name__ == '__main__':
     from {module} import {func}
     from {module} import {func}
     {func}()
     {func}()
 """
 """
-    
+
     def write_script(self, entrypt, target, extra_preamble=''):
     def write_script(self, entrypt, target, extra_preamble=''):
         """Write a launcher script from a 'module:function' entry point
         """Write a launcher script from a 'module:function' entry point
-        
+
         py_version and py_bitness are used to write an appropriate shebang line
         py_version and py_bitness are used to write an appropriate shebang line
         for the PEP 397 Windows launcher.
         for the PEP 397 Windows launcher.
         """
         """
@@ -306,11 +306,11 @@ if __name__ == '__main__':
 
 
     def prepare_shortcuts(self):
     def prepare_shortcuts(self):
         """Prepare shortcut files in the build directory.
         """Prepare shortcut files in the build directory.
-        
+
         If entry_point is specified, write the script. If script is specified,
         If entry_point is specified, write the script. If script is specified,
         copy to the build directory. Prepare target and parameters for these
         copy to the build directory. Prepare target and parameters for these
         shortcuts.
         shortcuts.
-        
+
         Also copies shortcut icons
         Also copies shortcut icons
         """
         """
         files = set()
         files = set()
@@ -346,12 +346,12 @@ if __name__ == '__main__':
             shutil.copy2(sc['icon'], self.build_dir)
             shutil.copy2(sc['icon'], self.build_dir)
             sc['icon'] = os.path.basename(sc['icon'])
             sc['icon'] = os.path.basename(sc['icon'])
             files.add(sc['icon'])
             files.add(sc['icon'])
-    
+
         self.install_files.extend([(f, '$INSTDIR') for f in files])
         self.install_files.extend([(f, '$INSTDIR') for f in files])
-    
+
     def prepare_packages(self):
     def prepare_packages(self):
         """Move requested packages into the build directory.
         """Move requested packages into the build directory.
-        
+
         If a pynsist_pkgs directory exists, it is copied into the build
         If a pynsist_pkgs directory exists, it is copied into the build
         directory as pkgs/ . Any packages not already there are found on
         directory as pkgs/ . Any packages not already there are found on
         sys.path and copied in.
         sys.path and copied in.
@@ -447,7 +447,7 @@ if __name__ == '__main__':
 
 
     def run_nsis(self):
     def run_nsis(self):
         """Runs makensis using the specified .nsi file
         """Runs makensis using the specified .nsi file
-        
+
         Returns the exit code.
         Returns the exit code.
         """
         """
         try:
         try:
@@ -480,15 +480,15 @@ if __name__ == '__main__':
             self.fetch_python()
             self.fetch_python()
             if self.py_version < '3.3':
             if self.py_version < '3.3':
                 self.fetch_pylauncher()
                 self.fetch_pylauncher()
-        
+
         self.prepare_shortcuts()
         self.prepare_shortcuts()
 
 
         if self.commands:
         if self.commands:
             self.prepare_commands()
             self.prepare_commands()
-        
+
         # Packages
         # Packages
         self.prepare_packages()
         self.prepare_packages()
-        
+
         # Extra files
         # Extra files
         self.copy_extra_files()
         self.copy_extra_files()
 
 
@@ -500,15 +500,44 @@ if __name__ == '__main__':
             if not exitcode:
             if not exitcode:
                 logger.info('Installer written to %s', pjoin(self.build_dir, self.installer_name))
                 logger.info('Installer written to %s', pjoin(self.build_dir, self.installer_name))
 
 
+
+def get_installer_builder_args(config):
+    def get_boolean(s):
+        if s.lower() in ('1', 'yes', 'true', 'on'):
+            return True
+        if s.lower() in ('0', 'no', 'false', 'off'):
+            return False
+        raise ValueError('ValueError: Not a boolean: {}'.format(s))
+
+    appcfg = config['Application']
+    args = {}
+    args['appname'] = appcfg['name'].strip()
+    args['version'] = appcfg['version'].strip()
+    args['publisher'] = appcfg['publisher'].strip() if 'publisher' in appcfg else None
+    args['icon'] = appcfg.get('icon', DEFAULT_ICON).strip()
+    args['packages'] = config.get('Include', 'packages', fallback='').strip().splitlines()
+    args['pypi_wheel_reqs'] = config.get('Include', 'pypi_wheels', fallback='').strip().splitlines()
+    args['extra_files'] = configreader.read_extra_files(config)
+    args['py_version'] = config.get('Python', 'version', fallback=DEFAULT_PY_VERSION).strip()
+    args['py_bitness'] = config.getint('Python', 'bitness', fallback=DEFAULT_BITNESS)
+    args['py_format'] = config.get('Python', 'format').strip() if 'format' in config['Python'] else None
+    inc_msvcrt = config.get('Python', 'include_msvcrt', fallback='True')
+    args['inc_msvcrt'] = get_boolean(inc_msvcrt.strip())
+    args['build_dir'] = config.get('Build', 'directory', fallback=DEFAULT_BUILD_DIR).strip()
+    args['installer_name'] = config.get('Build', 'installer_name') if 'installer_name' in config['Build'] else None
+    args['nsi_template'] = config.get('Build', 'nsi_template').strip() if 'nsi_template' in config['Build'] else None
+    args['exclude'] = config.get('Include', 'exclude', fallback='').strip().splitlines()
+    return args
+
 def main(argv=None):
 def main(argv=None):
     """Make an installer from the command line.
     """Make an installer from the command line.
-    
+
     This parses command line arguments and a config file, and calls
     This parses command line arguments and a config file, and calls
     :func:`all_steps` with the extracted information.
     :func:`all_steps` with the extracted information.
     """
     """
     logger.setLevel(logging.INFO)
     logger.setLevel(logging.INFO)
     logger.handlers = [logging.StreamHandler()]
     logger.handlers = [logging.StreamHandler()]
-    
+
     import argparse
     import argparse
     argp = argparse.ArgumentParser(prog='pynsist')
     argp = argparse.ArgumentParser(prog='pynsist')
     argp.add_argument('config_file')
     argp.add_argument('config_file')
@@ -516,7 +545,7 @@ def main(argv=None):
         help='Prepare files and folders, stop before calling makensis. For debugging.'
         help='Prepare files and folders, stop before calling makensis. For debugging.'
     )
     )
     options = argp.parse_args(argv)
     options = argp.parse_args(argv)
-    
+
     dirname, config_file = os.path.split(options.config_file)
     dirname, config_file = os.path.split(options.config_file)
     if dirname:
     if dirname:
         os.chdir(dirname)
         os.chdir(dirname)
@@ -530,28 +559,11 @@ def main(argv=None):
         logger.error('Error parsing configuration file:')
         logger.error('Error parsing configuration file:')
         logger.error(str(e))
         logger.error(str(e))
         sys.exit(1)
         sys.exit(1)
-    appcfg = cfg['Application']
+
+    args = get_installer_builder_args(cfg)
 
 
     try:
     try:
-        InstallerBuilder(
-            appname = appcfg['name'],
-            version = appcfg['version'],
-            publisher = appcfg.get('publisher', None),
-            icon = appcfg.get('icon', DEFAULT_ICON),
-            shortcuts = shortcuts,
-            commands=commands,
-            packages = cfg.get('Include', 'packages', fallback='').splitlines(),
-            pypi_wheel_reqs = cfg.get('Include', 'pypi_wheels', fallback='').splitlines(),
-            extra_files = configreader.read_extra_files(cfg),
-            py_version = cfg.get('Python', 'version', fallback=DEFAULT_PY_VERSION),
-            py_bitness = cfg.getint('Python', 'bitness', fallback=DEFAULT_BITNESS),
-            py_format = cfg.get('Python', 'format', fallback=None),
-            inc_msvcrt = cfg.getboolean('Python', 'include_msvcrt', fallback=True),
-            build_dir = cfg.get('Build', 'directory', fallback=DEFAULT_BUILD_DIR),
-            installer_name = cfg.get('Build', 'installer_name', fallback=None),
-            nsi_template = cfg.get('Build', 'nsi_template', fallback=None),
-            exclude = cfg.get('Include', 'exclude', fallback='').splitlines(),
-        ).run(makensis=(not options.no_makensis))
+        InstallerBuilder(**args).run(makensis=(not options.no_makensis))
     except InputError as e:
     except InputError as e:
         logger.error("Error in config values:")
         logger.error("Error in config values:")
         logger.error(str(e))
         logger.error(str(e))

+ 41 - 0
nsist/tests/data_files/valid_config_value_newline.cfg

@@ -0,0 +1,41 @@
+[Application]
+name=
+    My App
+version=
+    1.0
+publisher=
+    Test
+entry_point=
+    myapp:main
+icon=
+    myapp.ico
+
+[Python]
+version=
+    3.6.0
+bitness=
+    64
+format=
+    bundled
+include_msvcrt =
+    True
+
+[Build]
+directory=
+    build/
+nsi_template=
+    template.nsi
+
+[Include]
+packages =
+    requests
+    bs4
+pypi_wheels=
+    html5lib
+exclude=
+    something
+
+# Other files and folders that should be installed
+files =
+    LICENSE
+    data_files/

+ 62 - 4
nsist/tests/test_configuration_validator.py

@@ -1,17 +1,75 @@
-from nose.tools import *
+import configparser
 import os
 import os
+
+from nose.tools import *
+
 from .. import configreader
 from .. import configreader
-import configparser
+from .. import get_installer_builder_args
+
 
 
 DATA_FILES = os.path.join(os.path.dirname(__file__), 'data_files')
 DATA_FILES = os.path.join(os.path.dirname(__file__), 'data_files')
 
 
 def test_valid_config():
 def test_valid_config():
     configfile = os.path.join(DATA_FILES, 'valid_config.cfg')
     configfile = os.path.join(DATA_FILES, 'valid_config.cfg')
-    configreader.read_and_validate(configfile)
+    config = configreader.read_and_validate(configfile)
+    print(config['Application'])
+    print('Application' in config)
+    print(config.has_section('Application'))
+    # assert False
 
 
 def test_valid_config_with_shortcut():
 def test_valid_config_with_shortcut():
     configfile = os.path.join(DATA_FILES, 'valid_config_with_shortcut.cfg')
     configfile = os.path.join(DATA_FILES, 'valid_config_with_shortcut.cfg')
-    configreader.read_and_validate(configfile)
+    config = configreader.read_and_validate(configfile)
+
+def test_valid_config_with_values_starting_on_new_line():
+    configfile = os.path.join(DATA_FILES, 'valid_config_value_newline.cfg')
+    config = configreader.read_and_validate(configfile)
+    sections = ('Application', 'Python', 'Include', 'Build')
+    assert len(config.sections()) == len(sections)
+    for section in sections:
+        assert section in config
+        assert config.has_section(section)
+
+    assert config.get('Application', 'name') == '\nMy App'
+    assert config.get('Application', 'version') == '\n1.0'
+    assert config.get('Application', 'publisher') == '\nTest'
+    assert config.get('Application', 'entry_point') == '\nmyapp:main'
+    assert config.get('Application', 'icon') == '\nmyapp.ico'
+
+    assert config.get('Python', 'version') == '\n3.6.0'
+    assert config.get('Python', 'bitness') == '\n64'
+    assert config.get('Python', 'format') == '\nbundled'
+    assert config.get('Python', 'include_msvcrt') == '\nTrue'
+
+    assert config.get('Build', 'directory') == '\nbuild/'
+    assert config.get('Build', 'nsi_template') == '\ntemplate.nsi'
+
+    assert config.get('Include', 'packages') == '\nrequests\nbs4'
+    assert config.get('Include', 'pypi_wheels') == '\nhtml5lib'
+    assert config.get('Include', 'exclude') == '\nsomething'
+    assert config.get('Include', 'files') == '\nLICENSE\ndata_files/'
+
+    args = get_installer_builder_args(config)
+    assert args['appname'] == 'My App'
+    assert args['version'] == '1.0'
+    assert args['publisher'] == 'Test'
+    # assert args['entry_point'] == '\nmyapp:main'
+    assert args['icon'] == 'myapp.ico'
+
+    assert args['py_version'] == '3.6.0'
+    assert args['py_bitness'] == 64
+    assert args['py_format'] == 'bundled'
+    assert args['inc_msvcrt'] == True
+
+    assert args['build_dir'] == 'build/'
+    assert args['nsi_template'] == 'template.nsi'
+
+    assert args['packages'] == ['requests', 'bs4']
+    assert args['pypi_wheel_reqs'] == ['html5lib']
+    assert args['exclude'] == ['something']
+    assert args['extra_files'] == [('', '$INSTDIR'),
+                                    ('LICENSE', '$INSTDIR'),
+                                    ('data_files/', '$INSTDIR')]
 
 
 @raises(configreader.InvalidConfig)
 @raises(configreader.InvalidConfig)
 def test_invalid_config_keys():
 def test_invalid_config_keys():